// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// A library to help generate expression.
library;

import 'package:_fe_analyzer_shared/src/parser/parser.dart'
    show lengthForToken, lengthOfSpan;
import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
import 'package:kernel/ast.dart';
import 'package:kernel/names.dart'
    show
        ampersandName,
        barName,
        callName,
        caretName,
        divisionName,
        equalsName,
        indexGetName,
        indexSetName,
        leftShiftName,
        lengthName,
        minusName,
        multiplyName,
        mustacheName,
        percentName,
        plusName,
        rightShiftName,
        tripleShiftName;
import 'package:kernel/src/unaliasing.dart';
import 'package:kernel/text/ast_to_text.dart';
import 'package:kernel/type_algebra.dart';

import '../base/compiler_context.dart';
import '../base/constant_context.dart' show ConstantContext;
import '../base/lookup_result.dart';
import '../base/messages.dart';
import '../base/problems.dart';
import '../builder/builder.dart';
import '../builder/declaration_builders.dart';
import '../builder/factory_builder.dart';
import '../builder/member_builder.dart';
import '../builder/method_builder.dart';
import '../builder/named_type_builder.dart';
import '../builder/nullability_builder.dart';
import '../builder/prefix_builder.dart';
import '../builder/property_builder.dart';
import '../builder/type_builder.dart';
import '../source/check_helper.dart';
import '../source/source_member_builder.dart';
import '../source/stack_listener_impl.dart' show offsetForToken;
import 'constness.dart' show Constness;
import 'expression_generator_helper.dart';
import 'forest.dart';
import 'internal_ast.dart';
import 'load_library_builder.dart';
import 'utils.dart';

/// A generator represents a subexpression for which we can't yet build an
/// expression because we don't yet know the context in which it's used.
///
/// Once the context is known, a generator can be converted into an expression
/// by calling a `build` method.
///
/// For example, when building a kernel representation for `a[x] = b`, after
/// parsing `a[x]` but before parsing `= b`, we don't yet know whether to
/// generate an invocation of `operator[]` or `operator[]=`, so we create a
/// [Generator] object.  Later, after `= b` is parsed, [buildAssignment] will
/// be called.
abstract class Generator {
  /// Helper that provides access to contextual information.
  final ExpressionGeneratorHelper _helper;

  /// A token that defines a position subexpression that being built.
  final Token token;

  final int fileOffset;

  Generator(this._helper, this.token) : fileOffset = offsetForToken(token);

  /// Easy access to the [Forest] factory object.
  Forest get _forest => _helper.forest;

  // TODO(johnniwinther): Improve the semantic precision of this property or
  // remove it. It's unclear if the semantics is inconsistent. It's for instance
  // used both for the name of a variable in [VariableUseGenerator] and for
  // `[]` in [IndexedAccessGenerator], and while the former text occurs in the
  // underlying source code, the latter doesn't.
  String get _plainNameForRead;

  /// Internal name used for debugging.
  String get _debugName;

  /// The source uri for use in error messaging.
  Uri get _fileUri => _helper.uri;

  ProblemReporting get problemReporting => _helper.problemReporting;

  CompilerContext get compilerContext => _helper.compilerContext;

  /// Builds an [Expression] representing a read from the generator.
  ///
  /// The read of this subexpression does _not_ need to support a simultaneous
  /// write of the same subexpression.
  Expression buildSimpleRead();

  /// Builds an [Expression] representing an assignment with the generator on
  /// the LHS and [value] on the RHS.
  ///
  /// The returned expression evaluates to the assigned value, unless
  /// [voidContext] is true, in which case it may evaluate to anything.
  Expression buildAssignment(Expression value, {bool voidContext = false});

  /// Returns an [Expression] representing a null-aware assignment (`??=`) with
  /// the generator on the LHS and [value] on the RHS.
  ///
  /// The returned expression evaluates to the assigned value, unless
  /// [voidContext] is true, in which case it may evaluate to anything.
  ///
  /// [type] is the static type of the RHS.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  });

  /// Returns an [Expression] representing a compound assignment (e.g. `+=`)
  /// with the generator on the LHS and [value] on the RHS.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  });

  /// Returns an [Expression] representing a pre-increment or pre-decrement of
  /// the generator.
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildCompoundAssignment(
      binaryOperator,
      _forest.createIntLiteral(operatorOffset, 1),
      operatorOffset: operatorOffset,
      // TODO(johnniwinther): We are missing some void contexts here. For
      // instance `++a?.b;` is not providing a void context making it default
      // `true`.
      voidContext: voidContext,
      isPreIncDec: true,
    );
  }

  /// Returns an [Expression] representing a post-increment or post-decrement of
  /// the generator.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  });

  /// Returns a [Generator] or [Expression] representing an index access
  /// (e.g. `a[b]`) with the generator on the receiver and [index] as the
  /// index expression.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  });

  /// Returns an [Expression] representing a compile-time error.
  ///
  /// At runtime, an exception will be thrown.
  Expression _makeInvalidRead({
    required UnresolvedKind unresolvedKind,
    bool errorHasBeenReported = false,
  }) {
    return _helper.buildUnresolvedError(
      _plainNameForRead,
      fileOffset,
      kind: unresolvedKind,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  /// Returns an [Expression] representing a compile-time error wrapping
  /// [value].
  ///
  /// At runtime, [value] will be evaluated before throwing an exception.
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) {
    return _helper.buildUnresolvedError(
      _plainNameForRead,
      fileOffset,
      kind: UnresolvedKind.Setter,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  Expression buildForEffect() => buildSimpleRead();

  List<Initializer> buildFieldInitializer(Map<String, int>? initializedFields) {
    return <Initializer>[
      _helper.buildInvalidInitializer(
        _helper.buildProblem(
          message: codeInvalidInitializer,
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
        ),
      ),
    ];
  }

  /// Returns an expression, generator or initializer for an invocation of this
  /// subexpression with [typeArguments] and [arguments] at [offset]. Callers
  /// must pass `isInForest: true` iff [typeArguments] have already been added
  /// to [forest].
  ///
  /// For instance:
  /// * If this is a [PropertyAccessGenerator] for `a.b`, this will create
  ///   a [MethodInvocation] for `a.b(...)`.
  /// * If this is a [ThisAccessGenerator] for `this` in an initializer list,
  ///   this will create a [RedirectingInitializer] for `this(...)`.
  /// * If this is an [IncompleteErrorGenerator], this will return the error
  ///   generator itself.
  ///
  /// If the invocation has explicit type arguments
  /// [buildTypeWithResolvedArguments] called instead.
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  });

  Expression_Generator_Initializer buildSelectorAccess(
    Selector selector,
    int operatorOffset,
    bool isNullAware,
  ) {
    selector.reportNewAsSelector();
    if (selector is InvocationSelector) {
      return _helper.buildMethodInvocation(
        buildSimpleRead(),
        selector.name,
        selector.arguments,
        offsetForToken(selector.token),
        isNullAware: isNullAware,
        isConstantExpression: selector.isPotentiallyConstant,
      );
    } else {
      if (_helper.constantContext != ConstantContext.none &&
          selector.name != lengthName) {
        problemReporting.addProblem(
          codeNotAConstantExpression,
          fileOffset,
          token.length,
          _fileUri,
        );
      }
      return PropertyAccessGenerator.make(
        _helper,
        selector.token,
        buildSimpleRead(),
        selector.name,
        isNullAware,
      );
    }
  }

  Expression_Generator buildEqualsOperation(
    Token token,
    Expression right, {
    required bool isNot,
  }) {
    return _forest.createEquals(
      offsetForToken(token),
      buildSimpleRead(),
      right,
      isNot: isNot,
    );
  }

  Expression_Generator buildBinaryOperation(
    Token token,
    Name binaryName,
    Expression right,
  ) {
    return _forest.createBinary(
      offsetForToken(token),
      buildSimpleRead(),
      binaryName,
      right,
    );
  }

  Expression_Generator buildUnaryOperation(Token token, Name unaryName) {
    return _forest.createUnary(
      offsetForToken(token),
      unaryName,
      buildSimpleRead(),
    );
  }

  Expression_Generator applyTypeArguments(
    int fileOffset,
    List<TypeBuilder>? typeArguments,
  ) {
    return new Instantiation(
      buildSimpleRead(),
      _helper.buildDartTypeArguments(
        typeArguments,
        TypeUse.tearOffTypeArgument,
        allowPotentiallyConstantType: true,
      ),
    )..fileOffset = fileOffset;
  }

  /// Returns a [TypeBuilder] for this subexpression instantiated with the
  /// type [arguments]. If no type arguments are provided [arguments] is `null`.
  ///
  /// The type arguments have not been resolved and should be resolved to
  /// create a [TypeBuilder] for a valid type.
  TypeBuilder buildTypeWithResolvedArguments(
    NullabilityBuilder nullabilityBuilder,
    List<TypeBuilder>? arguments, {
    required bool allowPotentiallyConstantType,
    required bool performTypeCanonicalization,
  }) {
    Message message = codeNotAType.withArgumentsOld(token.lexeme);
    _helper.libraryBuilder.addProblem(
      message,
      fileOffset,
      lengthForToken(token),
      _fileUri,
    );
    return new NamedTypeBuilderImpl.forInvalidType(
      token.lexeme,
      nullabilityBuilder,
      message.withLocation(_fileUri, fileOffset, lengthForToken(token)),
    );
  }

  Expression_Generator qualifiedLookup(Token name) {
    return new UnexpectedQualifiedUseGenerator(
      _helper,
      name,
      this,
      errorHasBeenReported: false,
    );
  }

  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    return _helper.createInstantiationAndInvocation(
      () => buildSimpleRead(),
      typeArguments,
      _plainNameForRead,
      name,
      arguments,
      instantiationOffset: fileOffset,
      invocationOffset: nameLastToken.charOffset,
      inImplicitCreationContext: inImplicitCreationContext,
    );
  }

  void printOn(StringSink sink);

  @override
  String toString() {
    StringBuffer buffer = new StringBuffer();
    buffer.write(_debugName);
    buffer.write("(offset: ");
    buffer.write("${fileOffset}");
    printOn(buffer);
    buffer.write(")");
    return "$buffer";
  }
}

/// [VariableUseGenerator] represents the subexpression whose prefix is a
/// local variable or parameter name.
///
/// For instance:
///
///   method(a) {
///     var b;
///     a;         // a VariableUseGenerator is created for `a`.
///     b = a[];   // a VariableUseGenerator is created for `a` and `b`.
///     b();       // a VariableUseGenerator is created for `b`.
///     b.c = a.d; // a VariableUseGenerator is created for `a` and `b`.
///   }
///
/// If the variable is final or read-only (like a parameter in a catch clause) a
/// [ReadOnlyAccessGenerator] is created instead.
class VariableUseGenerator extends Generator {
  final ExpressionVariable variable;

  VariableUseGenerator(
    ExpressionGeneratorHelper helper,
    Token nameToken,
    this.variable,
  ) : assert(variable.isAssignable, 'Variable $variable is not assignable'),
      super(helper, nameToken);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "VariableUseGenerator";

  @override
  String get _plainNameForRead => variable.cosmeticName!;

  int get _nameOffset => fileOffset;

  @override
  Expression buildSimpleRead() {
    return _createRead();
  }

  Expression _createRead() {
    return _helper.createVariableGet(variable, fileOffset);
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(fileOffset, value);
  }

  void _checkAssignment(int offset) {
    if (_helper.isDeclaredInEnclosingCase(variable)) {
      problemReporting.addProblem(
        codePatternVariableAssignmentInsideGuard,
        offset,
        noLength,
        _fileUri,
      );
    }
  }

  Expression _createWrite(int offset, Expression value) {
    _checkAssignment(offset);
    _helper.registerVariableAssignment(variable);
    return new VariableSet(variable, value)..fileOffset = offset;
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Expression read = _createRead();
    Expression write = _createWrite(fileOffset, value);
    return new IfNullSet(read, write, forEffect: voidContext)
      ..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Expression binary = _helper.forest.createBinary(
      operatorOffset,
      _createRead(),
      binaryOperator,
      value,
    );
    return _createWrite(fileOffset, binary);
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool forEffect,
    required bool isPost,
  }) {
    _checkAssignment(_nameOffset);
    _helper.registerVariableRead(variable);
    _helper.registerVariableAssignment(variable);
    return new LocalIncDec(
      variable: variable as InternalExpressionVariable,
      forEffect: forEffect,
      isPost: isPost,
      isInc: binaryOperator == plusName,
      nameOffset: _nameOffset,
      operatorOffset: operatorOffset,
    )..fileOffset = _nameOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.forest.createExpressionInvocation(
      adjustForImplicitCall(_plainNameForRead, offset),
      buildSimpleRead(),
      arguments,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", variable: ");
    printNodeOn(variable, sink, syntheticNames: syntheticNames);
  }
}

/// A [VariableUseGenerator] subclass for late final for-in loop variables
///
/// The special case of late final for-in loop variables is determined by the
/// following requirements to the error reporting.
///
///   * Even though the loop can be executed only once, initializing the
///     variable exactly once, it is still reasonable to report the error for
///     assigning to the late variable.
///
///   * The variable should be considered assigned in the statements following
///     the loop.
///
/// To have both of the effect, [ForInLateFinalVariableUseGenerator] is emitted
/// for the assignments of such variables. It extends [VariableUseGenerator],
/// but reports an error on assignment, similarly to
/// [AbstractReadOnlyAccessGenerator].
class ForInLateFinalVariableUseGenerator extends VariableUseGenerator {
  ForInLateFinalVariableUseGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    ExpressionVariable variable,
  ) : super(helper, token, variable);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ForInLateFinalVariableUseGenerator";

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    InvalidExpression error = _helper.buildProblem(
      message: codeCannotAssignToFinalVariable.withArgumentsOld(
        variable.cosmeticName!,
      ),
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: lengthForToken(token),
    )..parent = variable;
    Expression assignment = super.buildAssignment(
      value,
      voidContext: voidContext,
    );
    if (assignment is VariableSet) {
      assignment.value = error..parent = assignment;
    }
    return assignment;
  }
}

/// A [PropertyAccessGenerator] represents a subexpression whose prefix is
/// an explicit property access.
///
/// For instance
///
///   method(a) {
///     a.b;      // a PropertyAccessGenerator is created for `a.b`.
///     a.b();    // a PropertyAccessGenerator is created for `a.b`.
///     a.b = c;  // a PropertyAccessGenerator is created for `a.b`.
///     a.b += c; // a PropertyAccessGenerator is created for `a.b`.
///   }
///
/// If the receiver is `this`, a [ThisPropertyAccessGenerator] is created
/// instead. If the access is null-aware, e.g. `a?.b`, a
/// [NullAwarePropertyAccessGenerator] is created instead.
class PropertyAccessGenerator extends Generator {
  /// The receiver expression. `a` in the examples in the class documentation.
  final Expression receiver;

  /// The name for the accessed property. `b` in the examples in the class
  /// documentation.
  final Name name;

  PropertyAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token nameToken,
    this.receiver,
    this.name,
  ) : super(helper, nameToken);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "PropertyAccessGenerator";

  @override
  String get _plainNameForRead => name.text;

  /// The file offset for the [name].
  int get _nameOffset => fileOffset;

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.buildMethodInvocation(receiver, name, arguments, offset);
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", receiver: ");
    printNodeOn(receiver, sink, syntheticNames: syntheticNames);
    sink.write(", name: ");
    sink.write(name.text);
  }

  @override
  Expression buildSimpleRead() {
    return _forest.createPropertyGet(
      fileOffset,
      receiver,
      name,
      isNullAware: false,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _helper.forest.createPropertySet(
      fileOffset,
      receiver,
      name,
      value,
      forEffect: voidContext,
      isNullAware: false,
    );
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullPropertySet(
      receiver,
      name,
      value,
      forEffect: voidContext,
      readOffset: fileOffset,
      writeOffset: fileOffset,
      isNullAware: false,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return new CompoundPropertySet(
      receiver: receiver,
      propertyName: name,
      binaryName: binaryOperator,
      value: value,
      forEffect: voidContext,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
      isNullAware: false,
    )..fileOffset = operatorOffset;
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool forEffect,
    required bool isPost,
  }) {
    return new PropertyIncDec(
      receiver,
      name,
      forEffect: forEffect,
      isInc: binaryOperator == plusName,
      isPost: isPost,
      isNullAware: false,
      operatorOffset: operatorOffset,
      nameOffset: _nameOffset,
    )..fileOffset = _nameOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: true,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  /// Creates a [Generator] for the access of property [name] on [receiver].
  static Generator make(
    ExpressionGeneratorHelper helper,
    Token token,
    Expression receiver,
    Name name,
    bool isNullAware,
  ) {
    if (helper.forest.isThisExpression(receiver)) {
      // Coverage-ignore-block(suite): Not run.
      return new ThisPropertyAccessGenerator(
        helper,
        token,
        name,
        thisVariable: null,
        thisOffset: receiver.fileOffset,
        isNullAware: isNullAware,
      );
    } else {
      return isNullAware
          ? new NullAwarePropertyAccessGenerator(helper, token, receiver, name)
          : new PropertyAccessGenerator(helper, token, receiver, name);
    }
  }
}

/// A [ThisPropertyAccessGenerator] represents a subexpression whose prefix is
/// an implicit or explicit access on `this`.
///
/// For instance
///
///   class C {
///     var b;
///     method() {
///       b;           // a ThisPropertyAccessGenerator is created for `b`.
///       b();         // a ThisPropertyAccessGenerator is created for `b`.
///       b = c;       // a ThisPropertyAccessGenerator is created for `b`.
///       b += c;      // a ThisPropertyAccessGenerator is created for `b`.
///       this.b;      // a ThisPropertyAccessGenerator is created for `this.b`.
///       this.b();    // a ThisPropertyAccessGenerator is created for `this.b`.
///       this.b = c;  // a ThisPropertyAccessGenerator is created for `this.b`.
///       this.b += c; // a ThisPropertyAccessGenerator is created for `this.b`.
///     }
///   }
///
/// This is a special case of [PropertyAccessGenerator] to avoid creating an
/// indirect access to 'this' in for instance `this.b += c` which by
/// [PropertyAccessGenerator] would have been created as
///
///     let #1 = this in #.b = #.b + c
///
/// instead of
///
///     this.b = this.b + c
///
class ThisPropertyAccessGenerator extends Generator {
  /// The name for the accessed property. `b` in the examples in the class
  /// documentation.
  final Name name;

  /// The offset of `this` if explicit. Otherwise `null`.
  final int? thisOffset;
  final bool isNullAware;

  /// The synthetic variable used for 'this' in instance extension members
  /// and instance extension type members/constructor bodies.
  VariableDeclaration? thisVariable;

  ThisPropertyAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token nameToken,
    this.name, {
    this.thisVariable,
    this.thisOffset,
    this.isNullAware = false,
  }) : super(helper, nameToken);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ThisPropertyAccessGenerator";

  @override
  String get _plainNameForRead => name.text;

  /// The file offset for the [name].
  int get _nameOffset => fileOffset;

  Expression get _thisExpression => thisVariable != null
      ? _forest.createVariableGet(thisOffset ?? fileOffset, thisVariable!)
      : _forest.createThisExpression(thisOffset ?? fileOffset);

  @override
  Expression buildSimpleRead() {
    return _createRead();
  }

  Expression _createRead() {
    return _forest.createPropertyGet(
      fileOffset,
      _thisExpression,
      name,
      isNullAware: false,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(fileOffset, value, forEffect: voidContext);
  }

  Expression _createWrite(
    int offset,
    Expression value, {
    required bool forEffect,
  }) {
    return _helper.forest.createPropertySet(
      fileOffset,
      _thisExpression,
      name,
      value,
      forEffect: forEffect,
      isNullAware: false,
    );
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullSet(
      _createRead(),
      _createWrite(offset, value, forEffect: voidContext),
      forEffect: voidContext,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Expression binary = _helper.forest.createBinary(
      operatorOffset,
      _createRead(),
      binaryOperator,
      value,
    );
    return _createWrite(fileOffset, binary, forEffect: voidContext);
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool forEffect,
    required bool isPost,
  }) {
    return new PropertyIncDec(
      _thisExpression,
      name,
      forEffect: forEffect,
      isInc: binaryOperator == plusName,
      isPost: isPost,
      isNullAware: false,
      operatorOffset: operatorOffset,
      nameOffset: _nameOffset,
    )..fileOffset = fileOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.buildMethodInvocation(
      _thisExpression,
      name,
      arguments,
      offset,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", name: ");
    sink.write(name.text);
  }
}

class NullAwarePropertyAccessGenerator extends Generator {
  final Expression receiver;

  final Name name;

  NullAwarePropertyAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token nameToken,
    this.receiver,
    this.name,
  ) : super(helper, nameToken);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "NullAwarePropertyAccessGenerator";

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => name.text;

  /// The file offset of the [name].
  int get _nameOffset => fileOffset;

  @override
  Expression buildSimpleRead() {
    return _forest.createPropertyGet(
      fileOffset,
      receiver,
      name,
      isNullAware: true,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _forest.createPropertySet(
      fileOffset,
      receiver,
      name,
      value,
      forEffect: voidContext,
      isNullAware: true,
    );
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullPropertySet(
      receiver,
      name,
      value,
      forEffect: voidContext,
      readOffset: fileOffset,
      writeOffset: fileOffset,
      isNullAware: true,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return new CompoundPropertySet(
      receiver: receiver,
      propertyName: name,
      binaryName: binaryOperator,
      value: value,
      forEffect: voidContext,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
      isNullAware: true,
    )..fileOffset = operatorOffset;
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool forEffect,
    required bool isPost,
  }) {
    return new PropertyIncDec(
      receiver,
      name,
      forEffect: forEffect,
      isInc: binaryOperator == plusName,
      isPost: isPost,
      isNullAware: true,
      operatorOffset: operatorOffset,
      nameOffset: _nameOffset,
    )..fileOffset = fileOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: true,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return unsupported("doInvocation", offset, _fileUri);
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", receiver: ");
    printNodeOn(receiver, sink, syntheticNames: syntheticNames);
    sink.write(", name: ");
    sink.write(name.text);
  }
}

class SuperPropertyAccessGenerator extends Generator {
  final Name name;

  final Member? getter;

  final Member? setter;

  SuperPropertyAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token nameToken,
    this.name,
    this.getter,
    this.setter,
  ) : super(helper, nameToken);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "SuperPropertyAccessGenerator";

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => name.text;

  int get _nameOffset => fileOffset;

  @override
  Expression buildSimpleRead() {
    return _createRead();
  }

  Expression _createRead() {
    Member? getter = this.getter;
    if (getter == null) {
      return _helper.buildUnresolvedError(
        name.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Getter,
      );
    } else {
      return new SuperPropertyGet(name, getter)..fileOffset = fileOffset;
    }
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(fileOffset, value);
  }

  Expression _createWrite(int offset, Expression value) {
    Member? setter = this.setter;
    if (setter == null) {
      return _helper.buildUnresolvedError(
        name.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Setter,
      );
    } else {
      return new SuperPropertySet(name, value, setter)..fileOffset = offset;
    }
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Expression binary = _helper.forest.createBinary(
      operatorOffset,
      _createRead(),
      binaryOperator,
      value,
    );
    return _createWrite(fileOffset, binary);
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool voidContext,
    required bool isPost,
  }) {
    Member? getter = this.getter;
    Member? setter = this.setter;
    if (getter == null) {
      return _helper.buildUnresolvedError(
        name.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Getter,
      );
    } else if (setter == null) {
      return _helper.buildUnresolvedError(
        name.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Setter,
      );
    }
    return new SuperIncDec(
      getter: getter,
      setter: setter,
      name: name,
      forEffect: voidContext,
      isPost: isPost,
      isInc: binaryOperator == plusName,
      nameOffset: _nameOffset,
      operatorOffset: operatorOffset,
    )..fileOffset = _nameOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPost: true,
    );
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullSet(
      _createRead(),
      _createWrite(fileOffset, value),
      forEffect: voidContext,
    )..fileOffset = offset;
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    if (_helper.constantContext != ConstantContext.none) {
      // TODO(brianwilkerson) Fix the length
      problemReporting.addProblem(
        codeNotAConstantExpression,
        offset,
        1,
        _fileUri,
      );
    }
    if (getter == null) {
      return _helper.buildUnresolvedError(
        name.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Method,
      );
    } else if (isFieldOrGetter(getter)) {
      return _helper.forest.createExpressionInvocation(
        offset,
        buildSimpleRead(),
        arguments,
      );
    } else {
      // TODO(ahe): This could be something like "super.property(...)" where
      // property is a setter.
      return unhandled(
        "${getter.runtimeType}",
        "doInvocation",
        offset,
        _fileUri,
      );
    }
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", name: ");
    sink.write(name.text);
    sink.write(", getter: ");
    printQualifiedNameOn(getter, sink);
    sink.write(", setter: ");
    printQualifiedNameOn(setter, sink);
  }
}

class IndexedAccessGenerator extends Generator {
  final Expression receiver;

  final Expression index;

  final bool isNullAware;

  IndexedAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.receiver,
    this.index, {
    required this.isNullAware,
  }) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => "[]";

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "IndexedAccessGenerator";

  @override
  Expression buildSimpleRead() {
    return _forest.createIndexGet(
      fileOffset,
      receiver,
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _forest.createIndexSet(
      fileOffset,
      receiver,
      index,
      value,
      forEffect: voidContext,
      isNullAware: isNullAware,
    );
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullIndexSet(
      receiver: receiver,
      index: index,
      value: value,
      readOffset: fileOffset,
      testOffset: offset,
      writeOffset: fileOffset,
      forEffect: voidContext,
      isNullAware: isNullAware,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return new CompoundIndexSet(
      receiver: receiver,
      index: index,
      binaryName: binaryOperator,
      value: value,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
      forEffect: voidContext,
      forPostIncDec: isPostIncDec,
      isNullAware: isNullAware,
    )..fileOffset = operatorOffset;
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    Expression value = _forest.createIntLiteral(operatorOffset, 1);
    return buildCompoundAssignment(
      binaryOperator,
      value,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPostIncDec: true,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.forest.createExpressionInvocation(
      arguments.fileOffset,
      buildSimpleRead(),
      arguments,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", receiver: ");
    printNodeOn(receiver, sink, syntheticNames: syntheticNames);
    sink.write(", index: ");
    printNodeOn(index, sink, syntheticNames: syntheticNames);
    sink.write(", isNullAware: ${isNullAware}");
  }

  static Generator make(
    ExpressionGeneratorHelper helper,
    Token token,
    Expression receiver,
    Expression index, {
    required bool isNullAware,
  }) {
    if (helper.forest.isThisExpression(receiver)) {
      // Coverage-ignore-block(suite): Not run.
      return new ThisIndexedAccessGenerator(
        helper,
        token,
        index,
        thisOffset: receiver.fileOffset,
        isNullAware: isNullAware,
      );
    } else {
      return new IndexedAccessGenerator(
        helper,
        token,
        receiver,
        index,
        isNullAware: isNullAware,
      );
    }
  }
}

/// Special case of [IndexedAccessGenerator] to avoid creating an indirect
/// access to 'this'.
class ThisIndexedAccessGenerator extends Generator {
  final Expression index;

  final int? thisOffset;
  final bool isNullAware;

  ThisIndexedAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.index, {
    this.thisOffset,
    this.isNullAware = false,
  }) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => "[]";

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ThisIndexedAccessGenerator";

  @override
  Expression buildSimpleRead() {
    Expression receiver = _helper.forest.createThisExpression(fileOffset);
    return _forest.createIndexGet(
      fileOffset,
      receiver,
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    Expression receiver = _helper.forest.createThisExpression(fileOffset);
    return _forest.createIndexSet(
      fileOffset,
      receiver,
      index,
      value,
      forEffect: voidContext,
      isNullAware: isNullAware,
    );
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Expression receiver = _helper.forest.createThisExpression(fileOffset);
    return new IfNullIndexSet(
      receiver: receiver,
      index: index,
      value: value,
      readOffset: fileOffset,
      testOffset: offset,
      writeOffset: fileOffset,
      forEffect: voidContext,
      isNullAware: isNullAware,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Expression receiver = _helper.forest.createThisExpression(fileOffset);
    return new CompoundIndexSet(
      receiver: receiver,
      index: index,
      binaryName: binaryOperator,
      value: value,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
      forEffect: voidContext,
      forPostIncDec: isPostIncDec,
      isNullAware: isNullAware,
    )..fileOffset = operatorOffset;
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    Expression value = _forest.createIntLiteral(operatorOffset, 1);
    return buildCompoundAssignment(
      binaryOperator,
      value,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPostIncDec: true,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.forest.createExpressionInvocation(
      offset,
      buildSimpleRead(),
      arguments,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", index: ");
    printNodeOn(index, sink, syntheticNames: syntheticNames);
  }
}

class SuperIndexedAccessGenerator extends Generator {
  final Expression index;

  final Procedure? getter;

  final Procedure? setter;

  SuperIndexedAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.index,
    this.getter,
    this.setter,
  ) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => "[]";

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "SuperIndexedAccessGenerator";

  @override
  Expression buildSimpleRead() {
    Procedure? getter = this.getter;
    if (getter == null) {
      return _helper.buildUnresolvedError(
        indexGetName.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Method,
        length: noLength,
      );
    } else {
      return _helper.forest.createSuperMethodInvocation(
        fileOffset,
        indexGetName,
        getter,
        _helper.forest.createArguments(fileOffset, <Expression>[index]),
      );
    }
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    Procedure? setter = this.setter;
    if (setter == null) {
      return _helper.buildUnresolvedError(
        indexSetName.text,
        fileOffset,
        isSuper: true,
        kind: UnresolvedKind.Method,
        length: noLength,
      );
    } else {
      if (voidContext) {
        return _helper.forest.createSuperMethodInvocation(
          fileOffset,
          indexSetName,
          setter,
          _helper.forest.createArguments(fileOffset, <Expression>[
            index,
            value,
          ]),
        );
      } else {
        return new SuperIndexSet(setter, index, value)..fileOffset = fileOffset;
      }
    }
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullSuperIndexSet(
      getter: getter,
      setter: setter,
      index: index,
      value: value,
      readOffset: fileOffset,
      testOffset: offset,
      writeOffset: fileOffset,
      forEffect: voidContext,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Procedure? getter = this.getter;
    Procedure? setter = this.setter;
    if (getter == null || setter == null) {
      return buildAssignment(
        buildBinaryOperation(token, binaryOperator, value),
      );
    } else {
      return new CompoundSuperIndexSet(
        getter: getter,
        setter: setter,
        index: index,
        binaryName: binaryOperator,
        value: value,
        readOffset: fileOffset,
        binaryOffset: operatorOffset,
        writeOffset: fileOffset,
        forEffect: voidContext,
        forPostIncDec: isPostIncDec,
      );
    }
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    Expression value = _forest.createIntLiteral(operatorOffset, 1);
    return buildCompoundAssignment(
      binaryOperator,
      value,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPostIncDec: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.forest.createExpressionInvocation(
      offset,
      buildSimpleRead(),
      arguments,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", index: ");
    printNodeOn(index, sink, syntheticNames: syntheticNames);
    sink.write(", getter: ");
    printQualifiedNameOn(getter, sink);
    sink.write(", setter: ");
    printQualifiedNameOn(setter, sink);
  }
}

/// A [StaticAccessGenerator] represents a subexpression whose prefix is
/// a static or top-level member, including static extension members.
///
/// For instance
///
///   get property => 0;
///   set property(_) {}
///   var field;
///   method() {}
///
///   main() {
///     property;     // a StaticAccessGenerator is created for `property`.
///     property = 0; // a StaticAccessGenerator is created for `property`.
///     field = 0;    // a StaticAccessGenerator is created for `field`.
///     method;       // a StaticAccessGenerator is created for `method`.
///     method();     // a StaticAccessGenerator is created for `method`.
///   }
///
///   class A {}
///   extension B on A {
///     static get property => 0;
///     static set property(_) {}
///     static var field;
///     static method() {
///       property;     // this StaticAccessGenerator is created for `property`.
///       property = 0; // this StaticAccessGenerator is created for `property`.
///       field = 0;    // this StaticAccessGenerator is created for `field`.
///       method;       // this StaticAccessGenerator is created for `method`.
///       method();     // this StaticAccessGenerator is created for `method`.
///     }
///   }
///
class StaticAccessGenerator extends Generator {
  /// The name of the original target;
  final Name targetName;

  /// The static [Member] used for performing a read on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have a readable target.
  /// For instance if the subexpression is a setter without a corresponding
  /// getter.
  final Member? readTarget;

  /// The static [Member] used for performing an invocation on this
  /// subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have an invokable target.
  /// For instance if the subexpression is a setter without a corresponding
  /// getter.
  final Member? invokeTarget;

  /// The static [Member] used for performing a write on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have a writable target.
  /// For instance if the subexpression is a final field, a method, or a getter
  /// without a corresponding setter.
  final Member? writeTarget;

  /// The offset of the type name if explicit. Otherwise `null`.
  final int? typeOffset;
  final bool isNullAware;

  StaticAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token nameToken,
    this.targetName,
    this.readTarget,
    this.invokeTarget,
    this.writeTarget, {
    this.typeOffset,
    this.isNullAware = false,
  }) : assert(
         readTarget != null || invokeTarget != null || writeTarget != null,
         "No targets for $targetName.",
       ),
       super(helper, nameToken);

  factory StaticAccessGenerator.fromBuilder(
    ExpressionGeneratorHelper helper,
    Name targetName,
    Token nameToken,
    MemberBuilder? getterBuilder,
    MemberBuilder? setterBuilder, {
    int? typeOffset,
    bool isNullAware = false,
  }) {
    // If both [getterBuilder] and [setterBuilder] exist, they must both be
    // either top level (potentially from different libraries) or from the same
    // class/extension.
    assert(
      getterBuilder == null ||
          setterBuilder == null ||
          getterBuilder.declarationBuilder == setterBuilder.declarationBuilder,
      "Invalid builders for $targetName: $getterBuilder vs $setterBuilder.",
    );
    return new StaticAccessGenerator(
      helper,
      nameToken,
      targetName,
      getterBuilder?.readTarget,
      getterBuilder?.invokeTarget,
      setterBuilder?.writeTarget,
      typeOffset: typeOffset,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "StaticAccessGenerator";

  @override
  String get _plainNameForRead => targetName.text;

  int get _nameOffset => fileOffset;

  @override
  Expression buildSimpleRead() {
    return _createRead();
  }

  Expression _createRead() {
    Expression read;
    Member? readTarget = this.readTarget;
    if (readTarget == null) {
      read = _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else {
      if (readTarget is Procedure && readTarget.kind == ProcedureKind.Method) {
        read = _helper.forest.createStaticTearOff(fileOffset, readTarget);
      } else {
        read = _helper.forest.createStaticGet(fileOffset, readTarget);
      }
    }
    return read;
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(fileOffset, value);
  }

  Expression _createWrite(int offset, Expression value) {
    Expression write;
    if (writeTarget == null) {
      write = _makeInvalidWrite();
    } else {
      write = new StaticSet(writeTarget!, value)..fileOffset = offset;
    }
    return write;
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return new IfNullSet(
      _createRead(),
      _createWrite(offset, value),
      forEffect: voidContext,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Expression binary = _helper.forest.createBinary(
      operatorOffset,
      _createRead(),
      binaryOperator,
      value,
    );
    return _createWrite(fileOffset, binary);
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool voidContext,
    required bool isPost,
  }) {
    Member? getter = readTarget;
    Member? setter = writeTarget;
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }

    return new StaticIncDec(
      getter: getter,
      setter: setter,
      name: targetName,
      forEffect: voidContext,
      isPost: isPost,
      isInc: binaryOperator == plusName,
      nameOffset: _nameOffset,
      operatorOffset: operatorOffset,
    )..fileOffset = _nameOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPost: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    if (_helper.constantContext != ConstantContext.none &&
        !_helper.isIdentical(invokeTarget) &&
        !_helper.libraryFeatures.constFunctions.isEnabled) {
      return _helper.buildProblem(
        message: codeNotConstantExpression.withArgumentsOld(
          'Method invocation',
        ),
        fileUri: _helper.uri,
        fileOffset: offset,
        length: invokeTarget?.name.text.length ?? 0,
      );
    }
    if (invokeTarget == null ||
        (readTarget != null && isFieldOrGetter(readTarget!))) {
      return _helper.forest.createExpressionInvocation(
        offset + (readTarget?.name.text.length ?? 0),
        buildSimpleRead(),
        arguments,
      );
    } else {
      return _helper.buildStaticInvocation(
        target: invokeTarget as Procedure,
        arguments: arguments,
        fileOffset: offset,
      );
    }
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", targetName: ");
    sink.write(targetName);
    sink.write(", readTarget: ");
    printQualifiedNameOn(readTarget, sink);
    sink.write(", writeTarget: ");
    printQualifiedNameOn(writeTarget, sink);
  }
}

/// An [ExtensionInstanceAccessGenerator] represents a subexpression whose
/// prefix is an extension instance member.
///
/// For instance
///
///   class A {}
///   extension B on A {
///     get property => 0;
///     set property(_) {}
///     method() {
///       property;     // this generator is created for `property`.
///       property = 0; // this generator is created for `property`.
///       method;       // this generator is created for `method`.
///       method();     // this generator is created for `method`.
///     }
///   }
///
/// These can only occur within an extension instance member.
class ExtensionInstanceAccessGenerator extends Generator {
  final Extension extension;

  /// The original name of the target.
  final Name targetName;

  /// The static [Member] generated for an instance extension member which is
  /// used for performing a read on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have a readable target.
  /// For instance if the subexpression is a setter without a corresponding
  /// getter.
  final Procedure? readTarget;

  /// The static [Member] generated for an instance extension member which is
  /// used for performing an invocation on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have an invokable target.
  /// For instance if the subexpression is a getter or setter.
  final Procedure? invokeTarget;

  /// The static [Member] generated for an instance extension member which is
  /// used for performing a write on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have a writable target.
  /// For instance if the subexpression is a final field, a method, or a getter
  /// without a corresponding setter.
  final Procedure? writeTarget;

  /// The parameter holding the value for `this` within the current extension
  /// instance method.
  // TODO(johnniwinther): Handle static access to extension instance members,
  // in which case the access is erroneous and [extensionThis] is `null`.
  final VariableDeclaration extensionThis;

  /// The type parameters synthetically added to  the current extension
  /// instance method.
  final List<TypeParameter>? extensionTypeParameters;

  ExtensionInstanceAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.extension,
    this.targetName,
    this.readTarget,
    this.invokeTarget,
    this.writeTarget,
    this.extensionThis,
    this.extensionTypeParameters,
  ) : assert(readTarget != null || invokeTarget != null || writeTarget != null),
      super(helper, token);

  factory ExtensionInstanceAccessGenerator.fromBuilder(
    ExpressionGeneratorHelper helper,
    Token token,
    Extension extension,
    Name targetName,
    VariableDeclaration extensionThis,
    List<TypeParameter>? extensionTypeParameters,
    MemberBuilder? getterBuilder,
    MemberBuilder? setterBuilder,
  ) {
    Procedure? readTarget;
    Procedure? invokeTarget;
    if (getterBuilder != null) {
      assert(!getterBuilder.isStatic);
      if (getterBuilder is PropertyBuilder) {
        assert(!getterBuilder.hasConcreteField);
        readTarget = getterBuilder.readTarget as Procedure?;
      } else if (getterBuilder is MethodBuilder) {
        if (getterBuilder.isOperator) {
          // Coverage-ignore-block(suite): Not run.
          invokeTarget = getterBuilder.invokeTarget as Procedure?;
        } else {
          readTarget = getterBuilder.readTarget as Procedure?;
          invokeTarget = getterBuilder.invokeTarget as Procedure?;
        }
      } else {
        return unhandled(
          "${getterBuilder.runtimeType}",
          "ExtensionInstanceAccessGenerator.fromBuilder",
          offsetForToken(token),
          helper.uri,
        );
      }
    }
    Procedure? writeTarget;
    if (setterBuilder != null) {
      if (setterBuilder is PropertyBuilder) {
        assert(!setterBuilder.isStatic && !setterBuilder.hasConcreteField);
        writeTarget = setterBuilder.writeTarget as Procedure?;
      } else {
        return unhandled(
          "${setterBuilder.runtimeType}",
          "ExtensionInstanceAccessGenerator.fromBuilder",
          offsetForToken(token),
          helper.uri,
        );
      }
    }
    return new ExtensionInstanceAccessGenerator(
      helper,
      token,
      extension,
      targetName,
      readTarget,
      invokeTarget,
      writeTarget,
      extensionThis,
      extensionTypeParameters,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "InstanceExtensionAccessGenerator";

  @override
  String get _plainNameForRead => targetName.text;

  /// Creates an access to the implicit `this` variable.
  Expression _createThisAccess() =>
      _helper.createVariableGet(extensionThis, fileOffset);

  /// Creates the implicit type arguments for the extension access. These
  /// are the type parameter type of the extension type parameters.
  List<DartType>? _createThisTypeArguments() {
    List<DartType>? extensionTypeArguments;
    if (extensionTypeParameters != null) {
      extensionTypeArguments = [];
      for (TypeParameter typeParameter in extensionTypeParameters!) {
        extensionTypeArguments.add(
          _forest.createTypeParameterTypeWithDefaultNullabilityForLibrary(
            typeParameter,
            extension.enclosingLibrary,
          ),
        );
      }
    }
    return extensionTypeArguments;
  }

  /// Returns `true` if performing a read operation is a tear off.
  ///
  /// This is the case if [invokeTarget] is non-null, since extension methods
  /// have both a [readTarget] and an [invokeTarget], whereas extension getters
  /// only have a [readTarget].
  bool get isReadTearOff => invokeTarget != null;

  @override
  Expression buildSimpleRead() {
    return _createRead();
  }

  Expression _createRead() {
    Procedure? getter = readTarget;
    Expression read;
    if (getter == null) {
      read = _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (isReadTearOff) {
      read = new ExtensionTearOff.implicit(
        extension: extension,
        thisTypeArguments: _createThisTypeArguments(),
        thisAccess: _createThisAccess(),
        name: targetName,
        tearOff: getter,
      )..fileOffset = fileOffset;
    } else {
      read = new ExtensionGet.implicit(
        extension: extension,
        thisTypeArguments: _createThisTypeArguments(),
        thisAccess: _createThisAccess(),
        name: targetName,
        getter: getter,
      )..fileOffset = fileOffset;
    }
    return read;
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(fileOffset, value, forEffect: voidContext);
  }

  Expression _createWrite(
    int offset,
    Expression value, {
    required bool forEffect,
  }) {
    Procedure? setter = writeTarget;
    if (setter == null) {
      return _makeInvalidWrite();
    } else {
      return new ExtensionSet.implicit(
        extension: extension,
        thisTypeArguments: _createThisTypeArguments(),
        thisAccess: _createThisAccess(),
        name: targetName,
        setter: setter,
        value: value,
        forEffect: forEffect,
      )..fileOffset = offset;
    }
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }
    return new ExtensionIfNullSet.implicit(
      extension: extension,
      thisTypeArguments: _createThisTypeArguments(),
      thisAccess: _createThisAccess(),
      propertyName: targetName,
      getter: getter,
      rhs: value,
      setter: setter,
      forEffect: voidContext,
      readOffset: fileOffset,
      binaryOffset: offset,
      writeOffset: fileOffset,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }
    return new ExtensionCompoundSet.implicit(
      extension: extension,
      thisTypeArguments: _createThisTypeArguments(),
      thisAccess: _createThisAccess(),
      propertyName: targetName,
      getter: getter,
      binaryName: binaryOperator,
      rhs: value,
      setter: setter,
      forEffect: voidContext,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
    );
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool forEffect,
    required bool isPost,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }
    return new ExtensionIncDec.implicit(
      extension: extension,
      thisTypeArguments: _createThisTypeArguments(),
      thisAccess: _createThisAccess(),
      name: targetName,
      getter: getter,
      setter: setter,
      isPost: isPost,
      isInc: binaryOperator == plusName,
      forEffect: forEffect,
    )..fileOffset = operatorOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    Procedure? method = invokeTarget;
    Procedure? getter = readTarget;
    if (method != null) {
      Expression thisAccess = _createThisAccess();
      List<TypeParameter> typeParameters = method.function.typeParameters;
      LocatedMessage? argMessage = problemReporting.checkArgumentsForFunction(
        function: method.function,
        arguments: arguments,
        fileOffset: offset,
        fileUri: _fileUri,
        typeParameters: typeParameters,
        extension: extension,
      );
      if (argMessage != null) {
        // Coverage-ignore-block(suite): Not run.
        return problemReporting.buildProblemWithContextFromMember(
          compilerContext: compilerContext,
          name: targetName.text,
          member: method,
          message: argMessage,
          fileUri: _fileUri,
        );
      }
      return new ExtensionMethodInvocation.implicit(
        extension: extension,
        thisTypeArguments: _createThisTypeArguments(),
        thisAccess: thisAccess,
        name: targetName,
        target: method,
        arguments: arguments,
      )..fileOffset = fileOffset;
    } else if (getter != null) {
      Expression thisAccess = _createThisAccess();
      return new ExtensionGetterInvocation.implicit(
        extension: extension,
        thisTypeArguments: _createThisTypeArguments(),
        thisAccess: thisAccess,
        name: targetName,
        target: getter,
        arguments: arguments,
      )..fileOffset = fileOffset;
    } else {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    }
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", targetName: ");
    sink.write(targetName);
    sink.write(", readTarget: ");
    printQualifiedNameOn(readTarget, sink);
    sink.write(", writeTarget: ");
    printQualifiedNameOn(writeTarget, sink);
  }
}

/// An [ExplicitExtensionInstanceAccessGenerator] represents a subexpression
/// whose prefix is a forced extension instance member access.
///
/// For instance
///
///   class A<T> {}
///   extension B on A<int> {
///     method() {}
///   }
///   extension C<T> {
///     T get field => 0;
///     set field(T _) {}
///   }
///
///   method(A a) {
///     B(a).method;     // this generator is created for `B(a).method`.
///     B(a).method();   // this generator is created for `B(a).method`.
///     C<int>(a).field; // this generator is created for `C<int>(a).field`.
///     C(a).field = 0;  // this generator is created for `C(a).field`.
///   }
///
class ExplicitExtensionInstanceAccessGenerator extends Generator {
  /// The file offset used for the explicit extension application type
  /// arguments.
  final int? extensionTypeArgumentOffset;

  final Extension extension;

  /// The name of the original target;
  final Name targetName;

  /// The static [Member] generated for an instance extension member which is
  /// used for performing a read on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have a readable target.
  /// For instance if the subexpression is a setter without a corresponding
  /// getter.
  final Procedure? readTarget;

  /// The static [Member] generated for an instance extension member which is
  /// used for performing an invocation on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have an invokable target.
  /// For instance if the subexpression is a getter or setter.
  final Procedure? invokeTarget;

  /// The static [Member] generated for an instance extension member which is
  /// used for performing a write on this subexpression.
  ///
  /// This can be `null` if the subexpression doesn't have a writable target.
  /// For instance if the subexpression is a final field, a method, or a getter
  /// without a corresponding setter.
  final Procedure? writeTarget;

  /// The expression holding the receiver value for the explicit extension
  /// access, that is, `a` in `Extension<int>(a).method<String>()`.
  final Expression receiver;

  /// The type arguments explicitly passed to the explicit extension access,
  /// like `<int>` in `Extension<int>(a).method<String>()`.
  final List<DartType>? explicitTypeArguments;

  /// The number of type parameters declared on the extension declaration.
  final int extensionTypeParameterCount;

  /// If `true` the access is null-aware, like `Extension(c)?.foo`.
  final bool isNullAware;

  ExplicitExtensionInstanceAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.extensionTypeArgumentOffset,
    this.extension,
    this.targetName,
    this.readTarget,
    this.invokeTarget,
    this.writeTarget,
    this.receiver,
    this.explicitTypeArguments,
    this.extensionTypeParameterCount, {
    required this.isNullAware,
  }) : assert(
         readTarget != null || invokeTarget != null || writeTarget != null,
       ),
       super(helper, token);

  factory ExplicitExtensionInstanceAccessGenerator.fromBuilder({
    required ExpressionGeneratorHelper helper,
    required Token token,
    required int? extensionTypeArgumentOffset,
    required Extension extension,
    required Name name,
    required MemberBuilder? getter,
    required MemberBuilder? setter,
    required Expression receiver,
    required List<DartType>? explicitTypeArguments,
    required int extensionTypeParameterCount,
    required bool isNullAware,
  }) {
    assert(getter != null || setter != null);
    Procedure? readTarget;
    Procedure? invokeTarget;
    if (getter != null) {
      assert(!getter.isStatic);
      if (getter is PropertyBuilder) {
        readTarget = getter.readTarget as Procedure?;
      } else if (getter is MethodBuilder) {
        if (getter.isOperator) {
          invokeTarget = getter.invokeTarget as Procedure?;
        } else {
          readTarget = getter.readTarget as Procedure?;
          invokeTarget = getter.invokeTarget as Procedure?;
        }
      } else {
        return unhandled(
          "$getter (${getter.runtimeType})",
          "InstanceExtensionAccessGenerator.fromBuilder",
          offsetForToken(token),
          helper.uri,
        );
      }
    }
    Procedure? writeTarget;
    if (setter != null) {
      assert(!setter.isStatic);
      if (setter is PropertyBuilder) {
        if (setter.hasSetter) {
          writeTarget = setter.writeTarget as Procedure?;
        }
      } else {
        return unhandled(
          "$setter (${setter.runtimeType})",
          "InstanceExtensionAccessGenerator.fromBuilder",
          offsetForToken(token),
          helper.uri,
        );
      }
    }
    return new ExplicitExtensionInstanceAccessGenerator(
      helper,
      token,
      extensionTypeArgumentOffset,
      extension,
      name,
      readTarget,
      invokeTarget,
      writeTarget,
      receiver,
      explicitTypeArguments,
      extensionTypeParameterCount,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ExplicitExtensionIndexedAccessGenerator";

  @override
  String get _plainNameForRead => targetName.text;

  /// Returns `true` if performing a read operation is a tear off.
  ///
  /// This is the case if [invokeTarget] is non-null, since extension methods
  /// have both a [readTarget] and an [invokeTarget], whereas extension getters
  /// only have a [readTarget].
  bool get isReadTearOff => invokeTarget != null;

  @override
  Expression buildSimpleRead() {
    return _createRead(receiver, isNullAware: isNullAware);
  }

  Expression _createRead(Expression receiver, {required bool isNullAware}) {
    Procedure? getter = readTarget;
    if (getter == null) {
      // Coverage-ignore-block(suite): Not run.
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (isReadTearOff) {
      return new ExtensionTearOff.explicit(
        extension: extension,
        explicitTypeArguments: explicitTypeArguments,
        receiver: receiver,
        name: targetName,
        tearOff: getter,
        isNullAware: isNullAware,
        extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      )..fileOffset = fileOffset;
    } else {
      return new ExtensionGet.explicit(
        extension: extension,
        explicitTypeArguments: explicitTypeArguments,
        receiver: receiver,
        name: targetName,
        getter: getter,
        isNullAware: isNullAware,
        extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      )..fileOffset = fileOffset;
    }
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(
      fileOffset,
      receiver,
      value,
      forEffect: voidContext,
      isNullAware: isNullAware,
    );
  }

  Expression _createWrite(
    int offset,
    Expression receiver,
    Expression value, {
    required bool forEffect,
    required bool isNullAware,
  }) {
    Procedure? setter = writeTarget;
    if (setter == null) {
      // Coverage-ignore-block(suite): Not run.
      return _makeInvalidWrite();
    } else {
      return new ExtensionSet.explicit(
        extension: extension,
        explicitTypeArguments: explicitTypeArguments,
        receiver: receiver,
        name: targetName,
        setter: setter,
        value: value,
        forEffect: forEffect,
        isNullAware: isNullAware,
        extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      )..fileOffset = offset;
    }
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }
    return new ExtensionIfNullSet.explicit(
      extension: extension,
      explicitTypeArguments: explicitTypeArguments,
      receiver: receiver,
      propertyName: targetName,
      getter: getter,
      rhs: value,
      setter: setter,
      forEffect: voidContext,
      readOffset: fileOffset,
      binaryOffset: offset,
      writeOffset: fileOffset,
      isNullAware: isNullAware,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }
    return new ExtensionCompoundSet.explicit(
      extension: extension,
      explicitTypeArguments: explicitTypeArguments,
      receiver: receiver,
      propertyName: targetName,
      getter: getter,
      binaryName: binaryOperator,
      rhs: value,
      setter: setter,
      forEffect: voidContext,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
      isNullAware: isNullAware,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    )..fileOffset = operatorOffset;
  }

  Expression _buildPrePostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    required bool forEffect,
    required bool isPost,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    if (getter == null) {
      // Coverage-ignore-block(suite): Not run.
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Setter);
    }
    return new ExtensionIncDec.explicit(
      extension: extension,
      explicitTypeArguments: explicitTypeArguments,
      receiver: receiver,
      name: targetName,
      getter: getter,
      setter: setter,
      isPost: isPost,
      isInc: binaryOperator == plusName,
      forEffect: forEffect,
      isNullAware: isNullAware,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    )..fileOffset = operatorOffset;
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: false,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _buildPrePostfixIncrement(
      binaryOperator,
      operatorOffset: operatorOffset,
      forEffect: voidContext,
      isPost: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    Procedure? method = invokeTarget;
    Procedure? getter = readTarget;
    if (method != null) {
      List<TypeParameter> typeParameters = method.function.typeParameters;
      LocatedMessage? argMessage = problemReporting.checkArgumentsForFunction(
        function: method.function,
        arguments: arguments,
        fileOffset: offset,
        fileUri: _fileUri,
        typeParameters: typeParameters,
        extension: extension,
      );
      if (argMessage != null) {
        return problemReporting.buildProblemWithContextFromMember(
          compilerContext: compilerContext,
          name: targetName.text,
          member: method,
          message: argMessage,
          fileUri: _fileUri,
        );
      }
      return new ExtensionMethodInvocation.explicit(
        extension: extension,
        explicitTypeArguments: explicitTypeArguments,
        receiver: receiver,
        name: targetName,
        target: method,
        arguments: arguments,
        isNullAware: isNullAware,
        extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      )..fileOffset = fileOffset;
    } else if (getter != null) {
      return new ExtensionGetterInvocation.explicit(
        extension: extension,
        explicitTypeArguments: explicitTypeArguments,
        receiver: receiver,
        name: targetName,
        target: getter,
        arguments: arguments,
        isNullAware: isNullAware,
        extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      )..fileOffset = fileOffset;
    } else {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Getter);
    }
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", targetName: ");
    sink.write(targetName);
    sink.write(", readTarget: ");
    printQualifiedNameOn(readTarget, sink);
    sink.write(", writeTarget: ");
    printQualifiedNameOn(writeTarget, sink);
  }
}

class ExplicitExtensionIndexedAccessGenerator extends Generator {
  /// The file offset used for the explicit extension application type
  /// arguments.
  final int? extensionTypeArgumentOffset;

  final Extension extension;

  /// The static [Member] generated for the [] operation.
  ///
  /// This can be `null` if the extension doesn't have an [] method.
  final Procedure? readTarget;

  /// The static [Member] generated for the []= operation.
  ///
  /// This can be `null` if the extension doesn't have an []= method.
  final Procedure? writeTarget;

  /// The expression holding the receiver value for the explicit extension
  /// access, that is, `a` in `Extension<int>(a)[index]`.
  final Expression receiver;

  /// The index expression;
  final Expression index;

  /// The type arguments explicitly passed to the explicit extension access,
  /// like `<int>` in `Extension<int>(a)[b]`.
  final List<DartType>? explicitTypeArguments;

  /// The number of type parameters declared on the extension declaration.
  final int extensionTypeParameterCount;

  final bool isNullAware;

  ExplicitExtensionIndexedAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.extensionTypeArgumentOffset,
    this.extension,
    this.readTarget,
    this.writeTarget,
    this.receiver,
    this.index,
    this.explicitTypeArguments,
    this.extensionTypeParameterCount, {
    required this.isNullAware,
  }) : assert(readTarget != null || writeTarget != null),
       super(helper, token);

  factory ExplicitExtensionIndexedAccessGenerator.fromBuilder(
    ExpressionGeneratorHelper helper,
    Token token,
    int? extensionTypeArgumentOffset,
    Extension extension,
    MemberBuilder? getterBuilder,
    MemberBuilder? setterBuilder,
    Expression receiver,
    Expression index,
    List<DartType>? explicitTypeArguments,
    int extensionTypeParameterCount, {
    required bool isNullAware,
  }) {
    Procedure? readTarget = getterBuilder?.invokeTarget as Procedure?;
    Procedure? writeTarget = setterBuilder?.invokeTarget as Procedure?;
    return new ExplicitExtensionIndexedAccessGenerator(
      helper,
      token,
      extensionTypeArgumentOffset,
      extension,
      readTarget,
      writeTarget,
      receiver,
      index,
      explicitTypeArguments,
      extensionTypeParameterCount,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => "[]";

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ExplicitExtensionIndexedAccessGenerator";

  @override
  Expression buildSimpleRead() {
    Procedure? getter = readTarget;
    if (getter == null) {
      // Coverage-ignore-block(suite): Not run.
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Method);
    }
    return new ExtensionIndexGet(
      extension,
      explicitTypeArguments,
      receiver,
      getter,
      index,
      isNullAware: isNullAware,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    )..fileOffset = fileOffset;
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    Procedure? setter = writeTarget;
    if (setter == null) {
      // Coverage-ignore-block(suite): Not run.
      return _makeInvalidWrite();
    }
    return new ExtensionIndexSet(
      extension,
      explicitTypeArguments,
      receiver,
      setter,
      index,
      value,
      isNullAware: isNullAware,
      forEffect: voidContext,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    )..fileOffset = fileOffset;
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Member);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Member);
    }

    return new ExtensionIfNullIndexSet(
      extension: extension,
      knownTypeArguments: explicitTypeArguments,
      receiver: receiver,
      getter: getter,
      setter: setter,
      index: index,
      value: value,
      readOffset: fileOffset,
      testOffset: offset,
      writeOffset: fileOffset,
      forEffect: voidContext,
      isNullAware: isNullAware,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    )..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    Procedure? getter = readTarget;
    Procedure? setter = writeTarget;
    // Coverage-ignore(suite): Not run.
    if (getter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Member);
    } else if (setter == null) {
      return _makeInvalidRead(unresolvedKind: UnresolvedKind.Member);
    }

    return new ExtensionCompoundIndexSet(
      extension: extension,
      explicitTypeArguments: explicitTypeArguments,
      receiver: receiver,
      getter: getter,
      setter: setter,
      index: index,
      binaryName: binaryOperator,
      rhs: value,
      readOffset: fileOffset,
      binaryOffset: operatorOffset,
      writeOffset: fileOffset,
      forEffect: voidContext,
      forPostIncDec: isPostIncDec,
      isNullAware: isNullAware,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
    );
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    Expression value = _forest.createIntLiteral(operatorOffset, 1);
    return buildCompoundAssignment(
      binaryOperator,
      value,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPostIncDec: true,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.forest.createExpressionInvocation(
      offset,
      buildSimpleRead(),
      arguments,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", index: ");
    printNodeOn(index, sink, syntheticNames: syntheticNames);
    sink.write(", readTarget: ");
    printQualifiedNameOn(readTarget, sink);
    sink.write(", writeTarget: ");
    printQualifiedNameOn(writeTarget, sink);
  }
}

/// An [ExplicitExtensionAccessGenerator] represents a subexpression whose
/// prefix is an explicit extension application.
///
/// For instance
///
///   class A<T> {}
///   extension B on A<int> {
///     method() {}
///   }
///   extension C<T> on A<T> {
///     T get field => 0;
///     set field(T _) {}
///   }
///
///   method(A a) {
///     B(a).method;     // this generator is created for `B(a)`.
///     B(a).method();   // this generator is created for `B(a)`.
///     C<int>(a).field; // this generator is created for `C<int>(a)`.
///     C(a).field = 0;  // this generator is created for `C(a)`.
///   }
///
/// When an access is performed on this generator a
/// [ExplicitExtensionInstanceAccessGenerator] is created.
class ExplicitExtensionAccessGenerator extends Generator {
  final ExtensionBuilder extensionBuilder;
  final Expression receiver;
  final List<DartType>? explicitTypeArguments;
  final int? extensionTypeArgumentOffset;

  ExplicitExtensionAccessGenerator({
    required ExpressionGeneratorHelper helper,
    required Token token,
    required this.extensionBuilder,
    required this.receiver,
    required this.explicitTypeArguments,
    required this.extensionTypeArgumentOffset,
  }) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead {
    return unsupported(
      "ExplicitExtensionAccessGenerator.plainNameForRead",
      fileOffset,
      _fileUri,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ExplicitExtensionAccessGenerator";

  @override
  Expression buildSimpleRead() {
    return _makeInvalidRead();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return _makeInvalidRead();
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _makeInvalidRead();
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _makeInvalidRead();
  }

  Generator _createInstanceAccess(
    Token token,
    Name name, {
    bool isNullAware = false,
  }) {
    MemberLookupResult? result = extensionBuilder.lookupExtensionMemberByName(
      name,
    );
    if (result == null) {
      return new UnresolvedNameGenerator(
        _helper,
        token,
        name,
        unresolvedReadKind: UnresolvedKind.Member,
      );
    }
    if (result.isInvalidLookup) {
      return new UnresolvedNameGenerator(
        _helper,
        token,
        name,
        unresolvedReadKind: UnresolvedKind.Member,
        errorHasBeenReported: true,
      );
    }
    if (result.isStatic) {
      return new UnresolvedNameGenerator(
        _helper,
        token,
        name,
        unresolvedReadKind: UnresolvedKind.Member,
      );
    }
    return new ExplicitExtensionInstanceAccessGenerator.fromBuilder(
      helper: _helper,
      token: token,
      extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      extension: extensionBuilder.extension,
      name: name,
      getter: result.getable,
      setter: result.setable,
      receiver: receiver,
      explicitTypeArguments: explicitTypeArguments,
      extensionTypeParameterCount: extensionBuilder.typeParameters?.length ?? 0,
      isNullAware: isNullAware,
    );
  }

  @override
  Expression_Generator buildSelectorAccess(
    Selector selector,
    int operatorOffset,
    bool isNullAware,
  ) {
    selector.reportNewAsSelector();
    if (_helper.constantContext != ConstantContext.none) {
      // Coverage-ignore-block(suite): Not run.
      problemReporting.addProblem(
        codeNotAConstantExpression,
        fileOffset,
        token.length,
        _fileUri,
      );
    }
    Generator generator = _createInstanceAccess(
      selector.token,
      selector.name,
      isNullAware: isNullAware,
    );
    if (selector.arguments != null) {
      return generator.doInvocation(
        offsetForToken(selector.token),
        selector.typeArguments,
        selector.arguments!,
        isTypeArgumentsInForest: selector.isTypeArgumentsInForest,
      );
    } else {
      return generator;
    }
  }

  @override
  Expression_Generator buildBinaryOperation(
    Token token,
    Name binaryName,
    Expression right,
  ) {
    int fileOffset = offsetForToken(token);
    Generator generator = _createInstanceAccess(token, binaryName);
    return generator.doInvocation(
      fileOffset,
      null,
      _forest.createArguments(fileOffset, <Expression>[right]),
    );
  }

  @override
  Expression_Generator buildUnaryOperation(Token token, Name unaryName) {
    int fileOffset = offsetForToken(token);
    Generator generator = _createInstanceAccess(token, unaryName);
    return generator.doInvocation(
      fileOffset,
      null,
      _forest.createArgumentsEmpty(fileOffset),
    );
  }

  @override
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    Generator generator = _createInstanceAccess(token, callName);
    return generator.doInvocation(
      offset,
      typeArguments,
      arguments,
      isTypeArgumentsInForest: isTypeArgumentsInForest,
    );
  }

  @override
  Expression _makeInvalidRead({
    UnresolvedKind? unresolvedKind,
    bool errorHasBeenReported = false,
  }) {
    return _helper.buildProblem(
      message: codeExplicitExtensionAsExpression,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: lengthForToken(token),
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) {
    return _helper.buildProblem(
      message: codeExplicitExtensionAsLvalue,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: lengthForToken(token),
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    MemberLookupResult? result = extensionBuilder.lookupExtensionMemberByName(
      indexGetName,
    );

    if (result == null) {
      // Coverage-ignore-block(suite): Not run.
      return new UnresolvedNameGenerator(
        _helper,
        token,
        indexGetName,
        unresolvedReadKind: UnresolvedKind.Method,
      );
    } else if (result.isInvalidLookup) {
      // Coverage-ignore-block(suite): Not run.
      return new UnresolvedNameGenerator(
        _helper,
        token,
        indexGetName,
        unresolvedReadKind: UnresolvedKind.Method,
        errorHasBeenReported: true,
      );
    }
    return new ExplicitExtensionIndexedAccessGenerator.fromBuilder(
      _helper,
      token,
      extensionTypeArgumentOffset,
      extensionBuilder.extension,
      result.getable,
      result.setable,
      receiver,
      index,
      explicitTypeArguments,
      extensionBuilder.typeParameters?.length ?? 0,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", extensionBuilder: ");
    sink.write(extensionBuilder);
    sink.write(", receiver: ");
    sink.write(receiver);
  }
}

class LoadLibraryGenerator extends Generator {
  final LoadLibraryBuilder builder;

  LoadLibraryGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.builder,
  ) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => 'loadLibrary';

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "LoadLibraryGenerator";

  @override
  Expression buildSimpleRead() {
    builder.importDependency.targetLibrary;
    LoadLibraryTearOff read = new LoadLibraryTearOff(
      builder.importDependency,
      builder.createTearoffMethod(_helper.forest),
    )..fileOffset = fileOffset;
    return read;
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Expression read = buildSimpleRead();
    Expression write = _makeInvalidWrite();
    return new IfNullSet(read, write, forEffect: voidContext)
      ..fileOffset = offset;
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    Expression value = _forest.createIntLiteral(operatorOffset, 1);
    return buildCompoundAssignment(
      binaryOperator,
      value,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPostIncDec: true,
    );
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    if (arguments.positional.length > 0 || arguments.named.length > 0) {
      _helper.addProblemErrorIfConst(
        codeLoadLibraryTakesNoArguments,
        offset,
        'loadLibrary'.length,
      );
    }
    return builder.createLoadLibrary(offset, _forest, arguments);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", builder: ");
    sink.write(builder);
  }
}

class DeferredAccessGenerator extends Generator {
  final PrefixUseGenerator prefixGenerator;

  final Generator suffixGenerator;

  DeferredAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.prefixGenerator,
    this.suffixGenerator,
  ) : super(helper, token);

  @override
  Expression buildSimpleRead() {
    return _helper.wrapInDeferredCheck(
      suffixGenerator.buildSimpleRead(),
      prefixGenerator.prefix,
      token.charOffset,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _helper.wrapInDeferredCheck(
      suffixGenerator.buildAssignment(value, voidContext: voidContext),
      prefixGenerator.prefix,
      token.charOffset,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return _helper.wrapInDeferredCheck(
      suffixGenerator.buildIfNullAssignment(
        value,
        type,
        offset,
        voidContext: voidContext,
      ),
      prefixGenerator.prefix,
      token.charOffset,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _helper.wrapInDeferredCheck(
      suffixGenerator.buildCompoundAssignment(
        binaryOperator,
        value,
        operatorOffset: operatorOffset,
        voidContext: voidContext,
        isPreIncDec: isPreIncDec,
        isPostIncDec: isPostIncDec,
      ),
      prefixGenerator.prefix,
      token.charOffset,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _helper.wrapInDeferredCheck(
      suffixGenerator.buildPostfixIncrement(
        binaryOperator,
        operatorOffset: operatorOffset,
        voidContext: voidContext,
      ),
      prefixGenerator.prefix,
      token.charOffset,
    );
  }

  @override
  Expression_Generator buildSelectorAccess(
    Selector selector,
    int operatorOffset,
    bool isNullAware,
  ) {
    selector.reportNewAsSelector();
    Object propertyAccess = suffixGenerator.buildSelectorAccess(
      selector,
      operatorOffset,
      isNullAware,
    );
    if (propertyAccess is Generator) {
      return new DeferredAccessGenerator(
        _helper,
        token,
        prefixGenerator,
        propertyAccess,
      );
    } else {
      Expression expression = propertyAccess as Expression;
      return _helper.wrapInDeferredCheck(
        expression,
        prefixGenerator.prefix,
        token.charOffset,
      );
    }
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead {
    return unsupported(
      "deferredAccessor.plainNameForRead",
      fileOffset,
      _fileUri,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "DeferredAccessGenerator";

  @override
  TypeBuilder buildTypeWithResolvedArguments(
    NullabilityBuilder nullabilityBuilder,
    List<TypeBuilder>? arguments, {
    required bool allowPotentiallyConstantType,
    required bool performTypeCanonicalization,
  }) {
    String name =
        "${prefixGenerator._plainNameForRead}."
        "${suffixGenerator._plainNameForRead}";
    TypeBuilder type = suffixGenerator.buildTypeWithResolvedArguments(
      nullabilityBuilder,
      arguments,
      allowPotentiallyConstantType: allowPotentiallyConstantType,
      performTypeCanonicalization: performTypeCanonicalization,
    );
    LocatedMessage message;
    TypeDeclarationBuilder? declaration = type.declaration;
    if (declaration is InvalidBuilder) {
      // Coverage-ignore-block(suite): Not run.
      message = declaration.message;
    } else {
      int charOffset = offsetForToken(prefixGenerator.token);
      message = codeDeferredTypeAnnotation
          .withArgumentsOld(
            _helper.buildDartType(
              type,
              TypeUse.deferredTypeError,
              allowPotentiallyConstantType: allowPotentiallyConstantType,
            ),
            prefixGenerator._plainNameForRead,
          )
          .withLocation(
            _fileUri,
            charOffset,
            lengthOfSpan(prefixGenerator.token, token),
          );
    }
    _helper.libraryBuilder.addProblem(
      message.messageObject,
      message.charOffset,
      message.length,
      message.uri,
    );
    return new NamedTypeBuilderImpl.forInvalidType(
      name,
      nullabilityBuilder,
      message,
    );
  }

  @override
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    Object suffix = suffixGenerator.doInvocation(
      offset,
      typeArguments,
      arguments,
      isTypeArgumentsInForest: isTypeArgumentsInForest,
    );
    if (suffix is Expression) {
      return _helper.wrapInDeferredCheck(
        suffix,
        prefixGenerator.prefix,
        fileOffset,
      );
    } else {
      return new DeferredAccessGenerator(
        _helper,
        token,
        prefixGenerator,
        suffix as Generator,
      );
    }
  }

  @override
  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    return _helper.wrapInDeferredCheck(
      suffixGenerator.invokeConstructor(
        typeArguments,
        name,
        arguments,
        nameToken,
        nameLastToken,
        constness,
        inImplicitCreationContext: inImplicitCreationContext,
      ),
      prefixGenerator.prefix,
      offsetForToken(suffixGenerator.token),
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", prefixGenerator: ");
    sink.write(prefixGenerator);
    sink.write(", suffixGenerator: ");
    sink.write(suffixGenerator);
  }
}

/// [TypeUseGenerator] represents the subexpression whose prefix is the name of
/// a class, enum, type parameter, typedef, mixin declaration, extension
/// declaration or built-in type, like dynamic and void.
///
/// For instance:
///
///   class A<T> {}
///   typedef B = Function();
///   mixin C<T> on A<T> {}
///   extension D<T> on A<T> {}
///
///   method<T>() {
///     C<B>        // a TypeUseGenerator is created for `C` and `B`.
///     B b;        // a TypeUseGenerator is created for `B`.
///     D.foo();    // a TypeUseGenerator is created for `D`.
///     new A<T>(); // a TypeUseGenerator is created for `A` and `T`.
///     T();        // a TypeUseGenerator is created for `T`.
///   }
///
class TypeUseGenerator extends AbstractReadOnlyAccessGenerator {
  final TypeDeclarationBuilder declaration;
  List<TypeBuilder>? typeArguments;

  final TypeName typeName;

  Expression? _expression;

  TypeUseGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.declaration,
    this.typeName,
  ) : super(
        helper,
        token,
        // TODO(johnniwinther): InvalidTypeDeclarationBuilder is currently
        // misused for import conflict.
        declaration is InvalidBuilder
            ? ReadOnlyAccessKind.InvalidDeclaration
            : ReadOnlyAccessKind.TypeLiteral,
      );

  @override
  String get targetName => typeName.name;

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "TypeUseGenerator";

  @override
  TypeBuilder buildTypeWithResolvedArguments(
    NullabilityBuilder nullabilityBuilder,
    List<TypeBuilder>? arguments, {
    required bool allowPotentiallyConstantType,
    required bool performTypeCanonicalization,
  }) {
    return new NamedTypeBuilderImpl(
      typeName,
      nullabilityBuilder,
      arguments: arguments,
      fileUri: _fileUri,
      charOffset: fileOffset,
      instanceTypeParameterAccess: _helper.instanceTypeParameterAccessState,
    )..bind(_helper.libraryBuilder, declaration);
  }

  @override
  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    return switch (_helper.resolveAndBuildConstructorInvocation(
      declaration,
      nameToken,
      nameLastToken,
      arguments,
      name,
      typeArguments,
      offsetForToken(nameToken),
      constness,
      unresolvedKind: UnresolvedKind.Constructor,
    )) {
      SuccessfulConstructorResolutionResult(:var constructorInvocation) =>
        constructorInvocation,
      ErroneousConstructorResolutionResult(:var errorExpression) =>
        errorExpression,
      UnresolvedConstructorResolutionResult unresolvedResult =>
        unresolvedResult.buildErrorExpression(),
    };
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", declaration: ");
    sink.write(declaration);
    sink.write(", plainNameForRead: ");
    sink.write(_plainNameForRead);
  }

  @override
  Expression get expression {
    if (_expression == null) {
      if (declaration is InvalidBuilder) {
        InvalidBuilder declaration = this.declaration as InvalidBuilder;
        _expression = _helper.buildProblemErrorIfConst(
          declaration.message.messageObject,
          fileOffset,
          token.length,
        );
      } else {
        _expression = _forest.createTypeLiteral(
          offsetForToken(token),
          _helper.buildDartType(
            buildTypeWithResolvedArguments(
              const NullabilityBuilder.omitted(),
              typeArguments,
              allowPotentiallyConstantType: true,
              performTypeCanonicalization: true,
            ),
            TypeUse.typeLiteral,
            allowPotentiallyConstantType:
                _helper.libraryFeatures.constructorTearoffs.isEnabled,
          ),
        );
      }
    }
    return _expression!;
  }

  MemberLookupResult? _findStaticExtensionMember(Name name) {
    MemberLookupResult? memberLookupResult;
    _helper.extensionScope.forEachExtension((
      ExtensionBuilder extensionBuilder,
    ) {
      // TODO(cstefantsova): Report an error on more than one found members.
      if (extensionBuilder.onType.declaration == declaration) {
        memberLookupResult = extensionBuilder.lookupExtensionMemberByName(name);
        if (memberLookupResult != null) {
          if (!memberLookupResult!.isStatic) {
            memberLookupResult = null;
          }
        }
      }
    });
    return memberLookupResult;
  }

  @override
  Expression_Generator buildSelectorAccess(
    Selector send,
    int operatorOffset,
    bool isNullAware,
  ) {
    int nameOffset = offsetForToken(send.token);
    Name name = send.name;
    ArgumentsImpl? arguments = send.arguments;

    TypeDeclarationBuilder? declarationBuilder = declaration;
    TypeAliasBuilder? aliasBuilder;
    List<TypeBuilder>? unaliasedTypeArguments;
    bool isGenericTypedefTearOff = false;
    if (declarationBuilder is TypeAliasBuilder) {
      aliasBuilder = declarationBuilder;
      declarationBuilder = aliasBuilder.unaliasDeclaration(
        null,
        isUsedAsClass: true,
        usedAsClassCharOffset: this.fileOffset,
        usedAsClassFileUri: _fileUri,
      );

      bool supportsConstructorTearOff =
          _helper.libraryFeatures.constructorTearoffs.isEnabled &&
          switch (declarationBuilder) {
            ClassBuilder() => true,
            ExtensionBuilder() => false,
            ExtensionTypeDeclarationBuilder() => true,
            // Coverage-ignore(suite): Not run.
            TypeAliasBuilder() => false,
            // Coverage-ignore(suite): Not run.
            NominalParameterBuilder() => false,
            // Coverage-ignore(suite): Not run.
            StructuralParameterBuilder() => false,
            // Coverage-ignore(suite): Not run.
            InvalidBuilder() => false,
            // Coverage-ignore(suite): Not run.
            BuiltinTypeDeclarationBuilder() => false,
            null => false,
          };
      bool isConstructorTearOff =
          send is PropertySelector && supportsConstructorTearOff;
      List<TypeBuilder>? aliasedTypeArguments = typeArguments
          ?.map(
            (unknownType) => _helper.validateTypeParameterUse(
              unknownType,
              allowPotentiallyConstantType: isConstructorTearOff,
            ),
          )
          .toList();
      if (aliasedTypeArguments != null &&
          aliasedTypeArguments.length != aliasBuilder.typeParametersCount) {
        // Coverage-ignore-block(suite): Not run.
        _helper.libraryBuilder.addProblem(
          codeTypeArgumentMismatch.withArgumentsOld(
            aliasBuilder.typeParametersCount,
          ),
          fileOffset,
          noLength,
          _fileUri,
        );
      } else {
        if (declarationBuilder is DeclarationBuilder) {
          if (aliasedTypeArguments != null) {
            new NamedTypeBuilderImpl(
                typeName,
                const NullabilityBuilder.omitted(),
                arguments: aliasedTypeArguments,
                fileUri: _fileUri,
                charOffset: fileOffset,
                instanceTypeParameterAccess:
                    _helper.instanceTypeParameterAccessState,
              )
              ..bind(_helper.libraryBuilder, aliasBuilder)
              ..build(_helper.libraryBuilder, TypeUse.instantiation);
          }

          // If the arguments weren't supplied, the tear off is treated as
          // generic, and the aliased type arguments match type parameters of
          // the type alias.
          if (aliasedTypeArguments == null &&
              aliasBuilder.typeParametersCount != 0) {
            isGenericTypedefTearOff = true;
            aliasedTypeArguments = <TypeBuilder>[];
            for (NominalParameterBuilder typeParameter
                in aliasBuilder.typeParameters!) {
              aliasedTypeArguments.add(
                new NamedTypeBuilderImpl(
                  new SyntheticTypeName(typeParameter.name, fileOffset),
                  const NullabilityBuilder.omitted(),
                  fileUri: _fileUri,
                  charOffset: fileOffset,
                  instanceTypeParameterAccess:
                      _helper.instanceTypeParameterAccessState,
                )..bind(_helper.libraryBuilder, typeParameter),
              );
            }
          }
          unaliasedTypeArguments = aliasBuilder.unaliasTypeArguments(
            aliasedTypeArguments,
          );
        }
      }
    }
    if (declarationBuilder is DeclarationBuilder) {
      MemberLookupResult? result = declarationBuilder.findStaticBuilder(
        name.text,
        nameOffset,
        _fileUri,
        _helper.libraryBuilder,
      );
      if (result != null && result.isInvalidLookup) {
        return new DuplicateDeclarationGenerator(
          _helper,
          send.token,
          result,
          name,
          name.text.length,
        );
      }

      Generator generator;
      bool supportsConstructorTearOff =
          _helper.libraryFeatures.constructorTearoffs.isEnabled &&
          switch (declarationBuilder) {
            ClassBuilder() => true,
            ExtensionBuilder() => false,
            ExtensionTypeDeclarationBuilder() => true,
          };

      if (result == null) {
        // TODO(johnniwinther): Update the comment below.
        // If we find a setter, [member] is a [SourcePropertyBuilder] or an
        // [AccessErrorBuilder], not null.
        if (send is PropertySelector) {
          assert(
            send.typeArguments == null,
            "Unexpected non-null typeArguments of "
            "an IncompletePropertyAccessGenerator object: "
            "'${send.typeArguments.runtimeType}'.",
          );
          if (supportsConstructorTearOff) {
            MemberLookupResult? result = declarationBuilder
                .findConstructorOrFactory(name.text, _helper.libraryBuilder);
            Expression? tearOffExpression;
            if (result != null && !result.isInvalidLookup) {
              MemberBuilder? constructor = result.getable;
              Member? tearOff = constructor?.readTarget;
              if (tearOff is Constructor) {
                if (declarationBuilder is ClassBuilder &&
                    declarationBuilder.isAbstract) {
                  return _helper.buildProblem(
                    message: codeAbstractClassConstructorTearOff,
                    fileUri: _helper.uri,
                    fileOffset: nameOffset,
                    length: name.text.length,
                  );
                } else if (declarationBuilder.isEnum) {
                  return _helper.buildProblem(
                    message: codeEnumConstructorTearoff,
                    fileUri: _helper.uri,
                    fileOffset: nameOffset,
                    length: name.text.length,
                  );
                }
                tearOffExpression = _helper.forest.createConstructorTearOff(
                  token.charOffset,
                  tearOff,
                );
              } else if (tearOff is Procedure) {
                if (tearOff.isRedirectingFactory) {
                  tearOffExpression = _helper.forest
                      .createRedirectingFactoryTearOff(
                        token.charOffset,
                        tearOff,
                      );
                } else if (tearOff.isFactory) {
                  tearOffExpression = _helper.forest.createConstructorTearOff(
                    token.charOffset,
                    tearOff,
                  );
                } else {
                  tearOffExpression = _helper.forest.createStaticTearOff(
                    token.charOffset,
                    tearOff,
                  );
                }
              } else if (tearOff != null) {
                unhandled(
                  "${tearOff.runtimeType}",
                  "buildPropertyAccess",
                  operatorOffset,
                  _helper.uri,
                );
              }
            }
            if (tearOffExpression != null) {
              List<DartType>? builtTypeArguments;
              if (unaliasedTypeArguments != null) {
                if (unaliasedTypeArguments.length !=
                    declarationBuilder.typeParametersCount) {
                  // The type arguments are either aren't provided or mismatch
                  // in number with the type parameters of the RHS declaration.
                  // We substitute them with the default types here: in the
                  // first case that would be exactly what type inference fills
                  // in for the RHS, and in the second case it's a reasonable
                  // fallback, as the error is reported during a check on the
                  // typedef.
                  builtTypeArguments = <DartType>[];
                  switch (declarationBuilder) {
                    case ClassBuilder():
                      for (TypeParameter typeParameter
                          in declarationBuilder.cls.typeParameters) {
                        builtTypeArguments.add(typeParameter.defaultType);
                      }
                    // Coverage-ignore(suite): Not run.
                    case ExtensionTypeDeclarationBuilder():
                      for (TypeParameter typeParameter
                          in declarationBuilder
                              .extensionTypeDeclaration
                              .typeParameters) {
                        builtTypeArguments.add(typeParameter.defaultType);
                      }
                    // Coverage-ignore(suite): Not run.
                    case ExtensionBuilder():
                      throw new UnsupportedError(
                        "Unexpected declaration $declarationBuilder",
                      );
                  }
                } else {
                  builtTypeArguments = unaliasTypes(
                    declarationBuilder.buildAliasedTypeArguments(
                      _helper.libraryBuilder,
                      unaliasedTypeArguments,
                      /* hierarchy = */ null,
                    ),
                  )!;
                }
              } else if (typeArguments != null) {
                builtTypeArguments = _helper.buildDartTypeArguments(
                  typeArguments,
                  TypeUse.tearOffTypeArgument,
                  allowPotentiallyConstantType: true,
                );
              }
              if (isGenericTypedefTearOff) {
                if (isProperRenameForTypeDeclaration(
                  _helper.typeEnvironment,
                  aliasBuilder!.typedef,
                  aliasBuilder.libraryBuilder.library,
                )) {
                  return tearOffExpression;
                }
                Procedure? tearOffLowering = aliasBuilder
                    .findConstructorOrFactory(
                      name.text,
                      nameOffset,
                      _fileUri,
                      _helper.libraryBuilder,
                    );
                if (tearOffLowering != null) {
                  if (tearOffLowering.isFactory) {
                    // Coverage-ignore-block(suite): Not run.
                    return _helper.forest.createConstructorTearOff(
                      token.charOffset,
                      tearOffLowering,
                    );
                  } else {
                    return _helper.forest.createStaticTearOff(
                      token.charOffset,
                      tearOffLowering,
                    );
                  }
                }
                FreshStructuralParametersFromTypeParameters
                freshTypeParameters =
                    getFreshStructuralParametersFromTypeParameters(
                      aliasBuilder.typedef.typeParameters,
                    );
                List<DartType>? substitutedTypeArguments;
                if (builtTypeArguments != null) {
                  substitutedTypeArguments = <DartType>[];
                  for (DartType builtTypeArgument in builtTypeArguments) {
                    substitutedTypeArguments.add(
                      freshTypeParameters.substitute(builtTypeArgument),
                    );
                  }
                }
                substitutedTypeArguments = unaliasTypes(
                  substitutedTypeArguments,
                );

                tearOffExpression = _helper.forest.createTypedefTearOff(
                  token.charOffset,
                  freshTypeParameters.freshTypeParameters,
                  tearOffExpression,
                  substitutedTypeArguments ?? const <DartType>[],
                );
              } else {
                if (builtTypeArguments != null &&
                    builtTypeArguments.isNotEmpty) {
                  builtTypeArguments = unaliasTypes(builtTypeArguments)!;

                  tearOffExpression = _helper.forest.createInstantiation(
                    token.charOffset,
                    tearOffExpression,
                    builtTypeArguments,
                  );
                }
              }
              return tearOffExpression;
            }
          }

          MemberLookupResult? memberLookupResult =
              _helper.libraryFeatures.staticExtensions.isEnabled
              ? _findStaticExtensionMember(name)
              : null;
          if (memberLookupResult != null) {
            if (memberLookupResult.isInvalidLookup) {
              // Coverage-ignore-block(suite): Not run.
              generator = new UnresolvedNameGenerator(
                _helper,
                send.token,
                name,
                unresolvedReadKind: UnresolvedKind.Member,
                errorHasBeenReported: true,
              );
            } else {
              generator = new StaticAccessGenerator.fromBuilder(
                _helper,
                name,
                send.token,
                memberLookupResult.getable,
                memberLookupResult.setable,
                typeOffset: fileOffset,
                isNullAware: isNullAware,
              );
            }
          } else {
            generator = new UnresolvedNameGenerator(
              _helper,
              send.token,
              name,
              unresolvedReadKind: UnresolvedKind.Member,
            );
          }
        } else {
          switch (_helper.resolveAndBuildConstructorInvocation(
            declarationBuilder,
            send.token,
            send.token,
            arguments!,
            name.text,
            send.typeArguments,
            token.charOffset,
            Constness.implicit,
            isTypeArgumentsInForest: send.isTypeArgumentsInForest,
            typeAliasBuilder: aliasBuilder,
            unresolvedKind: isNullAware
                ? UnresolvedKind.Method
                : UnresolvedKind.Member,
          )) {
            case SuccessfulConstructorResolutionResult(
              :var constructorInvocation,
            ):
              return constructorInvocation;
            case ErroneousConstructorResolutionResult(
              // Coverage-ignore(suite): Not run.
              :var errorExpression,
            ):
              return errorExpression;
            case UnresolvedConstructorResolutionResult unresolvedResult:
              MemberLookupResult? memberLookupResult =
                  _helper.libraryFeatures.staticExtensions.isEnabled
                  ? _findStaticExtensionMember(name)
                  : null;
              if (memberLookupResult != null) {
                if (memberLookupResult.isInvalidLookup) {
                  // Coverage-ignore-block(suite): Not run.
                  generator = new UnresolvedNameGenerator(
                    _helper,
                    send.token,
                    name,
                    unresolvedReadKind: UnresolvedKind.Member,
                    errorHasBeenReported: true,
                  );
                } else {
                  generator = new StaticAccessGenerator.fromBuilder(
                    _helper,
                    name,
                    send.token,
                    memberLookupResult.getable,
                    memberLookupResult.setable,
                    typeOffset: fileOffset,
                    isNullAware: isNullAware,
                  );
                }
              } else {
                return unresolvedResult.buildErrorExpression();
              }
          }
        }
      } else {
        Builder? getable = result.getable;
        Builder? setable = result.setable;
        if (getable != null) {
          if (getable.isStatic &&
              getable is! FactoryBuilder &&
              typeArguments != null) {
            return _helper.buildProblem(
              message: codeStaticTearOffFromInstantiatedClass,
              fileUri: _helper.uri,
              fileOffset: send.fileOffset,
              length: send.name.text.length,
            );
          } else {
            generator = new StaticAccessGenerator.fromBuilder(
              _helper,
              name,
              send.token,
              getable is MemberBuilder ? getable : null,
              setable is MemberBuilder ? setable : null,
              typeOffset: fileOffset,
              isNullAware: isNullAware,
            );
          }
        } else {
          generator = new StaticAccessGenerator.fromBuilder(
            _helper,
            name,
            send.token,
            getable is MemberBuilder ? getable : null,
            setable is MemberBuilder ? setable : null,
            typeOffset: fileOffset,
            isNullAware: isNullAware,
          );
        }
      }

      return arguments == null
          ? generator
          : generator.doInvocation(
              offsetForToken(send.token),
              send.typeArguments,
              arguments,
              isTypeArgumentsInForest: send.isTypeArgumentsInForest,
            );
    } else {
      // `SomeType?.toString` is the same as `SomeType.toString`, not
      // `(SomeType).toString`.
      return super.buildSelectorAccess(send, operatorOffset, isNullAware);
    }
  }

  @override
  Expression_Generator_Builder doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    if (declaration is ExtensionBuilder) {
      ExtensionBuilder extensionBuilder = declaration as ExtensionBuilder;
      if (arguments.positional.length != 1 || arguments.named.isNotEmpty) {
        return _helper.buildProblem(
          message: codeExplicitExtensionArgumentMismatch,
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
        );
      }
      List<DartType>? explicitTypeArguments = arguments.explicitTypeArguments;
      int? extensionTypeArgumentOffset;
      if (explicitTypeArguments != null) {
        int typeParameterCount = extensionBuilder.typeParameters?.length ?? 0;
        if (explicitTypeArguments.length != typeParameterCount) {
          return _helper.buildProblem(
            message: codeExplicitExtensionTypeArgumentMismatch.withArgumentsOld(
              extensionBuilder.name,
              typeParameterCount,
            ),
            fileUri: _helper.uri,
            fileOffset: fileOffset,
            length: lengthForToken(token),
          );
        }
        // TODO(johnniwinther): Provide the type arguments offsets.
        extensionTypeArgumentOffset = arguments.fileOffset;
      }
      // TODO(johnniwinther): Check argument and type argument count.
      return new ExplicitExtensionAccessGenerator(
        helper: _helper,
        token: token,
        extensionBuilder: declaration as ExtensionBuilder,
        receiver: arguments.positional.single,
        explicitTypeArguments: explicitTypeArguments,
        extensionTypeArgumentOffset: extensionTypeArgumentOffset,
      );
    } else {
      return switch (_helper.resolveAndBuildConstructorInvocation(
        declaration,
        token,
        token,
        arguments,
        "",
        typeArguments,
        token.charOffset,
        Constness.implicit,
        isTypeArgumentsInForest: isTypeArgumentsInForest,
        unresolvedKind: UnresolvedKind.Constructor,
      )) {
        SuccessfulConstructorResolutionResult(:var constructorInvocation) =>
          constructorInvocation,
        ErroneousConstructorResolutionResult(:var errorExpression) =>
          errorExpression,
        UnresolvedConstructorResolutionResult unresolvedResult =>
          unresolvedResult.buildErrorExpression(),
      };
    }
  }

  @override
  Expression_Generator applyTypeArguments(
    int fileOffset,
    List<TypeBuilder>? typeArguments,
  ) {
    return new TypeUseGenerator(_helper, token, declaration, typeName)
      ..typeArguments = typeArguments;
  }
}

enum ReadOnlyAccessKind {
  ConstVariable,
  FinalVariable,
  ExtensionThis,
  LetVariable,
  TypeLiteral,
  ParenthesizedExpression,
  InvalidDeclaration,
}

/// [ReadOnlyAccessGenerator] represents the subexpression whose prefix is the
/// name of final local variable, final parameter, or catch clause variable or
/// `this` in an instance method in an extension declaration.
///
/// For instance:
///
///   method(final a) {
///     final b = null;
///     a;         // a ReadOnlyAccessGenerator is created for `a`.
///     a[];       // a ReadOnlyAccessGenerator is created for `a`.
///     b();       // a ReadOnlyAccessGenerator is created for `b`.
///     b.c = a.d; // a ReadOnlyAccessGenerator is created for `a` and `b`.
///
///     try {
///     } catch (a) {
///       a;       // a ReadOnlyAccessGenerator is created for `a`.
///     }
///   }
///
///   extension on Foo {
///     method() {
///       this;         // a ReadOnlyAccessGenerator is created for `this`.
///       this.a;       // a ReadOnlyAccessGenerator is created for `this`.
///       this.b();     // a ReadOnlyAccessGenerator is created for `this`.
///     }
///   }
///
class ReadOnlyAccessGenerator extends AbstractReadOnlyAccessGenerator {
  @override
  final String targetName;

  @override
  Expression expression;

  ReadOnlyAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.expression,
    this.targetName,
    ReadOnlyAccessKind kind,
  ) : super(helper, token, kind);
}

abstract class AbstractReadOnlyAccessGenerator extends Generator {
  final ReadOnlyAccessKind kind;

  AbstractReadOnlyAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.kind,
  ) : super(helper, token);

  String get targetName;

  Expression get expression;

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ReadOnlyAccessGenerator";

  @override
  String get _plainNameForRead => targetName;

  @override
  Expression buildSimpleRead() => _createRead();

  Expression _createRead() => expression;

  @override
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) {
    switch (kind) {
      case ReadOnlyAccessKind.ConstVariable:
        return _helper.buildProblem(
          message: codeCannotAssignToConstVariable.withArgumentsOld(targetName),
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
          errorHasBeenReported: errorHasBeenReported,
        );
      case ReadOnlyAccessKind.FinalVariable:
        return _helper.buildProblem(
          message: codeCannotAssignToFinalVariable.withArgumentsOld(targetName),
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
          errorHasBeenReported: errorHasBeenReported,
        );
      case ReadOnlyAccessKind.ExtensionThis:
        return _helper.buildProblem(
          message: codeCannotAssignToExtensionThis,
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
          errorHasBeenReported: errorHasBeenReported,
        );
      case ReadOnlyAccessKind.TypeLiteral:
        return _helper.buildProblem(
          message: codeCannotAssignToTypeLiteral,
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
          errorHasBeenReported: errorHasBeenReported,
        );
      case ReadOnlyAccessKind.ParenthesizedExpression:
        return _helper.buildProblem(
          message: codeCannotAssignToParenthesizedExpression,
          fileUri: _helper.uri,
          fileOffset: fileOffset,
          length: lengthForToken(token),
          errorHasBeenReported: errorHasBeenReported,
        );
      case ReadOnlyAccessKind.LetVariable:
      case ReadOnlyAccessKind.InvalidDeclaration:
        break;
    }
    return super._makeInvalidWrite(errorHasBeenReported: errorHasBeenReported);
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _makeInvalidWrite();
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    Expression read = _createRead();
    Expression write = _makeInvalidWrite();
    return new IfNullSet(read, write, forEffect: voidContext)
      ..fileOffset = offset;
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _makeInvalidWrite();
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    Expression value = _forest.createIntLiteral(operatorOffset, 1);
    return buildCompoundAssignment(
      binaryOperator,
      value,
      operatorOffset: operatorOffset,
      voidContext: voidContext,
      isPostIncDec: true,
    );
  }

  @override
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.forest.createExpressionInvocation(
      adjustForImplicitCall(targetName, offset),
      _createRead(),
      arguments,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    // TODO(johnniwinther): The read-only quality of the variable should be
    // passed on to the generator.
    return new IndexedAccessGenerator(
      _helper,
      token,
      _createRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    NameSystem syntheticNames = new NameSystem();
    sink.write(", expression: ");
    printNodeOn(expression, sink, syntheticNames: syntheticNames);
    sink.write(", plainNameForRead: ");
    sink.write(targetName);
    sink.write(", kind: ");
    sink.write(kind);
  }
}

abstract class ErroneousExpressionGenerator extends Generator {
  ErroneousExpressionGenerator(ExpressionGeneratorHelper helper, Token token)
    : super(helper, token);

  InvalidExpression buildError({
    Arguments? arguments,
    required UnresolvedKind kind,
    int? charOffset,
    bool errorHasBeenReported = false,
  });

  // Coverage-ignore(suite): Not run.
  Name get name => unsupported("name", fileOffset, _fileUri);

  @override
  String get _plainNameForRead => name.text;

  @override
  List<Initializer> buildFieldInitializer(Map<String, int>? initializedFields) {
    return <Initializer>[
      _helper.buildInvalidInitializer(buildError(kind: UnresolvedKind.Setter)),
    ];
  }

  @override
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return buildError(
      arguments: arguments,
      charOffset: offset,
      kind: UnresolvedKind.Method,
    );
  }

  @override
  Expression_Generator buildSelectorAccess(
    Selector send,
    int operatorOffset,
    bool isNullAware,
  ) {
    return send.withReceiver(
      buildSimpleRead(),
      operatorOffset,
      isNullAware: isNullAware,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return buildError(kind: UnresolvedKind.Setter);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return buildError(kind: UnresolvedKind.Getter);
  }

  @override
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildError(
      arguments: _forest.createArguments(fileOffset, <Expression>[
        _forest.createIntLiteral(operatorOffset, 1),
      ]),
      kind: UnresolvedKind.Getter,
    )..fileOffset = operatorOffset;
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildError(
      arguments: _forest.createArguments(fileOffset, <Expression>[
        _forest.createIntLiteral(operatorOffset, 1),
      ]),
      kind: UnresolvedKind.Getter,
    )..fileOffset = operatorOffset;
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return buildError(kind: UnresolvedKind.Setter);
  }

  @override
  Expression buildSimpleRead() {
    return buildError(kind: UnresolvedKind.Member);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidRead({
    required UnresolvedKind unresolvedKind,
    bool errorHasBeenReported = false,
  }) {
    return buildError(
      kind: unresolvedKind,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) {
    return buildError(
      kind: UnresolvedKind.Setter,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    if (typeArguments != null) {
      assert(arguments.types.isEmpty);
      arguments.setExplicitTypeArguments(
        _helper.buildDartTypeArguments(
          typeArguments,
          TypeUse.constructorTypeArgument,
          allowPotentiallyConstantType: false,
        ),
      );
    }
    return buildError(arguments: arguments, kind: UnresolvedKind.Constructor);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }
}

class DuplicateDeclarationGenerator extends ErroneousExpressionGenerator {
  final LookupResult _lookupResult;
  @override
  final Name name;
  final int _nameLength;

  DuplicateDeclarationGenerator(
    super.helper,
    super.token,
    this._lookupResult,
    this.name,
    this._nameLength,
  );

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => 'DuplicateDeclarationGenerator';

  LocatedMessage _createInvalidMessage() {
    return LookupResult.createDuplicateMessage(
      _lookupResult,
      name: name.text,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: _nameLength,
    );
  }

  InvalidExpression _createInvalidExpression() {
    return LookupResult.createDuplicateExpression(
      _lookupResult,
      context: _helper.libraryBuilder.loader.target.context,
      name: name.text,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: _nameLength,
    );
  }

  @override
  InvalidExpression buildError({
    Arguments? arguments,
    required UnresolvedKind kind,
    int? charOffset,
    bool errorHasBeenReported = false,
  }) {
    return _createInvalidExpression();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidRead({
    UnresolvedKind? unresolvedKind,
    bool errorHasBeenReported = false,
  }) {
    return _createInvalidExpression();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) {
    return _createInvalidExpression();
  }

  @override
  // Coverage-ignore(suite): Not run.
  List<Initializer> buildFieldInitializer(Map<String, int>? initializedFields) {
    return <Initializer>[
      _helper.buildInvalidInitializer(_createInvalidExpression()),
    ];
  }

  @override
  Expression_Generator qualifiedLookup(Token name) {
    return new UnexpectedQualifiedUseGenerator(
      _helper,
      name,
      this,
      errorHasBeenReported: true,
    );
  }

  @override
  TypeBuilder buildTypeWithResolvedArguments(
    NullabilityBuilder nullabilityBuilder,
    List<TypeBuilder>? arguments, {
    required bool allowPotentiallyConstantType,
    required bool performTypeCanonicalization,
  }) {
    return new NamedTypeBuilderImpl.forInvalidType(
      token.lexeme,
      nullabilityBuilder,
      _createInvalidMessage(),
    );
  }

  @override
  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    return _createInvalidExpression();
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", name: ");
    sink.write(name.text);
  }
}

class UnresolvedNameGenerator extends ErroneousExpressionGenerator {
  @override
  final Name name;

  final UnresolvedKind unresolvedReadKind;

  final bool errorHasBeenReported;

  factory UnresolvedNameGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    Name name, {
    required UnresolvedKind unresolvedReadKind,
    bool errorHasBeenReported = false,
  }) {
    if (name.text.isEmpty) {
      unhandled("empty", "name", offsetForToken(token), helper.uri);
    }
    return new UnresolvedNameGenerator.internal(
      helper,
      token,
      name,
      unresolvedReadKind,
      errorHasBeenReported,
    );
  }

  UnresolvedNameGenerator.internal(
    ExpressionGeneratorHelper helper,
    Token token,
    this.name,
    this.unresolvedReadKind,
    this.errorHasBeenReported,
  ) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "UnresolvedNameGenerator";

  @override
  Expression doInvocation(
    int charOffset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return buildError(
      arguments: arguments,
      charOffset: charOffset,
      kind: UnresolvedKind.Method,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  InvalidExpression buildError({
    Arguments? arguments,
    required UnresolvedKind kind,
    int? charOffset,
    bool errorHasBeenReported = false,
  }) {
    charOffset ??= fileOffset;
    return _helper.buildUnresolvedError(
      _plainNameForRead,
      charOffset,
      kind: kind,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  Expression_Generator qualifiedLookup(Token name) {
    return new UnexpectedQualifiedUseGenerator(
      _helper,
      name,
      this,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _buildUnresolvedVariableAssignment(false, value);
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _buildUnresolvedVariableAssignment(true, value);
  }

  @override
  Expression buildSimpleRead() {
    return buildError(
      kind: unresolvedReadKind,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", name: ");
    sink.write(name.text);
  }

  Expression _buildUnresolvedVariableAssignment(
    bool isCompound,
    Expression value,
  ) {
    return buildError(
      kind: UnresolvedKind.Setter,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }
}

abstract class ContextAwareGenerator extends Generator {
  final Generator generator;

  ContextAwareGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.generator,
  ) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead {
    return unsupported("plainNameForRead", token.charOffset, _helper.uri);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Never doInvocation(
    int charOffset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return unhandled("${runtimeType}", "doInvocation", charOffset, _fileUri);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Never _makeInvalidRead({
    UnresolvedKind? unresolvedKind,
    bool errorHasBeenReported = false,
  }) {
    return unsupported("makeInvalidRead", token.charOffset, _helper.uri);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) {
    return _helper.buildProblem(
      message: codeIllegalAssignmentToNonAssignable,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: lengthForToken(token),
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }
}

class DelayedAssignment extends ContextAwareGenerator {
  final Expression value;

  String assignmentOperator;

  DelayedAssignment(
    ExpressionGeneratorHelper helper,
    Token token,
    Generator generator,
    this.value,
    this.assignmentOperator,
  ) : super(helper, token, generator);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "DelayedAssignment";

  @override
  Expression buildSimpleRead() {
    return handleAssignment(false);
  }

  @override
  Expression buildForEffect() {
    return handleAssignment(true);
  }

  Expression handleAssignment(bool voidContext) {
    if (_helper.constantContext != ConstantContext.none) {
      return _helper.buildProblem(
        message: codeNotAConstantExpression,
        fileUri: _helper.uri,
        fileOffset: fileOffset,
        length: token.length,
      );
    }
    if (identical("=", assignmentOperator)) {
      return generator.buildAssignment(value, voidContext: voidContext);
    } else if (identical("+=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        plusName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("-=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        minusName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("*=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        multiplyName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("%=", assignmentOperator)) {
      // Coverage-ignore-block(suite): Not run.
      return generator.buildCompoundAssignment(
        percentName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("&=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        ampersandName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("/=", assignmentOperator)) {
      // Coverage-ignore-block(suite): Not run.
      return generator.buildCompoundAssignment(
        divisionName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("<<=", assignmentOperator)) {
      // Coverage-ignore-block(suite): Not run.
      return generator.buildCompoundAssignment(
        leftShiftName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical(">>=", assignmentOperator)) {
      // Coverage-ignore-block(suite): Not run.
      return generator.buildCompoundAssignment(
        rightShiftName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical(">>>=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        tripleShiftName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("??=", assignmentOperator)) {
      return generator.buildIfNullAssignment(
        value,
        const DynamicType(),
        fileOffset,
        voidContext: voidContext,
      );
    }
    // Coverage-ignore(suite): Not run.
    else if (identical("^=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        caretName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("|=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        barName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else if (identical("~/=", assignmentOperator)) {
      return generator.buildCompoundAssignment(
        mustacheName,
        value,
        operatorOffset: fileOffset,
        voidContext: voidContext,
      );
    } else {
      return unhandled(
        assignmentOperator,
        "handleAssignment",
        token.charOffset,
        _helper.uri,
      );
    }
  }

  @override
  List<Initializer> buildFieldInitializer(Map<String, int>? initializedFields) {
    if (!identical("=", assignmentOperator) ||
        generator is! ThisPropertyAccessGenerator) {
      return generator.buildFieldInitializer(initializedFields);
    }
    return _helper.createFieldInitializer(
      generator._plainNameForRead,
      offsetForToken(generator.token),
      fileOffset,
      value,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", value: ");
    printNodeOn(value, sink);
    sink.write(", assignmentOperator: ");
    sink.write(assignmentOperator);
  }
}

class DelayedPostfixIncrement extends ContextAwareGenerator {
  final Name binaryOperator;

  DelayedPostfixIncrement(
    ExpressionGeneratorHelper helper,
    Token token,
    Generator generator,
    this.binaryOperator,
  ) : super(helper, token, generator);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "DelayedPostfixIncrement";

  @override
  Expression buildSimpleRead() {
    return generator.buildPostfixIncrement(
      binaryOperator,
      operatorOffset: fileOffset,
      voidContext: false,
    );
  }

  @override
  Expression buildForEffect() {
    return generator.buildPostfixIncrement(
      binaryOperator,
      operatorOffset: fileOffset,
      voidContext: true,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", binaryOperator: ");
    sink.write(binaryOperator.text);
  }
}

class PrefixUseGenerator extends Generator {
  final PrefixBuilder prefix;

  PrefixUseGenerator(ExpressionGeneratorHelper helper, Token token, this.prefix)
    : super(helper, token);

  @override
  String get _plainNameForRead => prefix.name;

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "PrefixUseGenerator";

  @override
  Expression buildSimpleRead() => _makeInvalidRead();

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _makeInvalidWrite();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return _makeInvalidRead();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _makeInvalidRead();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _makeInvalidRead();
  }

  @override
  Generator qualifiedLookup(Token nameToken) {
    if (_helper.constantContext != ConstantContext.none && prefix.deferred) {
      problemReporting.addProblem(
        codeCantUseDeferredPrefixAsConstant.withArgumentsOld(token),
        fileOffset,
        lengthForToken(token),
        _fileUri,
      );
    }
    String name = nameToken.lexeme;
    Generator result = _helper.processLookupResult(
      lookupResult: prefix.prefixScope.lookup(name),
      name: name,
      nameToken: nameToken,
      nameOffset: nameToken.charOffset,
      prefix: prefix,
      prefixToken: token,
      forStatementScope: false,
    );

    if (prefix.deferred) {
      if (result is! LoadLibraryGenerator) {
        result = new DeferredAccessGenerator(_helper, nameToken, this, result);
      }
    }
    return result;
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return problemReporting.wrapInLocatedProblem(
      compilerContext: compilerContext,
      expression: _helper.evaluateArgumentsBefore(
        arguments,
        _forest.createNullLiteral(fileOffset),
      ),
      message: codeCantUsePrefixAsExpression.withLocation(
        _helper.uri,
        fileOffset,
        lengthForToken(token),
      ),
    );
  }

  @override
  Expression_Generator buildSelectorAccess(
    Selector selector,
    int operatorOffset,
    bool isNullAware,
  ) {
    assert(
      selector.name.text == selector.token.lexeme,
      "'${selector.name.text}' != ${selector.token.lexeme}",
    );
    selector.reportNewAsSelector();
    Object result = qualifiedLookup(selector.token);
    if (selector is InvocationSelector) {
      result = _helper.finishSend(
        result,
        selector.typeArguments,
        selector.arguments,
        selector.fileOffset,
        isTypeArgumentsInForest: selector.isTypeArgumentsInForest,
      );
    }
    if (isNullAware) {
      result = problemReporting.wrapInLocatedProblem(
        compilerContext: compilerContext,
        expression: _helper.toValue(result),
        message: codeCantUsePrefixWithNullAware.withLocation(
          _helper.uri,
          fileOffset,
          lengthForToken(token),
        ),
      );
    }
    return result;
  }

  @override
  Expression _makeInvalidRead({
    UnresolvedKind? unresolvedKind,
    bool errorHasBeenReported = false,
  }) {
    return _helper.buildProblem(
      message: codeCantUsePrefixAsExpression,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: lengthForToken(token),
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) =>
      _makeInvalidRead(errorHasBeenReported: errorHasBeenReported);

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", prefix: ");
    sink.write(prefix.name);
    sink.write(", deferred: ");
    sink.write(prefix.deferred);
  }
}

class UnexpectedQualifiedUseGenerator extends Generator {
  final Generator prefixGenerator;

  /// If `true` an error has already been reported.
  final bool errorHasBeenReported;

  UnexpectedQualifiedUseGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.prefixGenerator, {
    required this.errorHasBeenReported,
  }) : super(helper, token);

  @override
  String get _plainNameForRead =>
      "${prefixGenerator._plainNameForRead}.${token.lexeme}";

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "UnexpectedQualifiedUseGenerator";

  @override
  Expression buildSimpleRead() => _makeInvalidRead(
    unresolvedKind: UnresolvedKind.Member,
    errorHasBeenReported: errorHasBeenReported,
  );

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _makeInvalidWrite(errorHasBeenReported: errorHasBeenReported);
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return _makeInvalidRead(
      unresolvedKind: UnresolvedKind.Member,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return _makeInvalidRead(
      unresolvedKind: UnresolvedKind.Member,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return _makeInvalidRead(
      unresolvedKind: UnresolvedKind.Member,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return _helper.buildUnresolvedError(
      _plainNameForRead,
      fileOffset,
      kind: UnresolvedKind.Method,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  TypeBuilder buildTypeWithResolvedArguments(
    NullabilityBuilder nullabilityBuilder,
    List<TypeBuilder>? arguments, {
    required bool allowPotentiallyConstantType,
    required bool performTypeCanonicalization,
  }) {
    Message message = codeNotAPrefixInTypeAnnotation.withArgumentsOld(
      prefixGenerator.token.lexeme,
      token.lexeme,
    );
    if (!errorHasBeenReported) {
      _helper.libraryBuilder.addProblem(
        message,
        offsetForToken(prefixGenerator.token),
        lengthOfSpan(prefixGenerator.token, token),
        _fileUri,
      );
    }
    return new NamedTypeBuilderImpl.forInvalidType(
      _plainNameForRead,
      nullabilityBuilder,
      message.withLocation(
        _fileUri,
        offsetForToken(prefixGenerator.token),
        lengthOfSpan(prefixGenerator.token, token),
      ),
    );
  }

  @override
  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    Message message = codeConstructorNotFound.withArgumentsOld(
      _helper.constructorNameForDiagnostics(name, className: _plainNameForRead),
    );
    return _helper.buildProblem(
      message: message,
      fileUri: _helper.uri,
      fileOffset: offsetForToken(prefixGenerator.token),
      length: lengthOfSpan(prefixGenerator.token, nameLastToken),
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", prefixGenerator: ");
    prefixGenerator.printOn(sink);
  }
}

class ParserErrorGenerator extends Generator {
  final Message message;

  ParserErrorGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.message,
  ) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => "#parser-error";

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ParserErrorGenerator";

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {}

  InvalidExpression buildProblem() {
    return buildProblemExpression(_helper, message, fileOffset);
  }

  static InvalidExpression buildProblemExpression(
    ExpressionGeneratorHelper _helper,
    Message message,
    int fileOffset,
  ) {
    return _helper.buildProblem(
      message: message,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: noLength,
      errorHasBeenReported: true,
    );
  }

  @override
  Expression buildSimpleRead() => buildProblem();

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidRead({
    UnresolvedKind? unresolvedKind,
    bool errorHasBeenReported = false,
  }) => buildProblem();

  @override
  // Coverage-ignore(suite): Not run.
  Expression _makeInvalidWrite({bool errorHasBeenReported = false}) =>
      buildProblem();

  @override
  List<Initializer> buildFieldInitializer(Map<String, int>? initializedFields) {
    return <Initializer>[_helper.buildInvalidInitializer(buildProblem())];
  }

  @override
  Expression doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    return buildProblem();
  }

  @override
  Expression buildSelectorAccess(
    Selector send,
    int operatorOffset,
    bool isNullAware,
  ) {
    return buildProblem();
  }

  @override
  TypeBuilder buildTypeWithResolvedArguments(
    NullabilityBuilder nullabilityBuilder,
    List<TypeBuilder>? arguments, {
    required bool allowPotentiallyConstantType,
    required bool performTypeCanonicalization,
  }) {
    _helper.libraryBuilder.addProblem(message, fileOffset, noLength, _fileUri);
    return new NamedTypeBuilderImpl.forInvalidType(
      token.lexeme,
      nullabilityBuilder,
      message.withLocation(_fileUri, fileOffset, noLength),
    );
  }

  TypeBuilder buildTypeWithResolvedArgumentsDoNotAddProblem(
    NullabilityBuilder nullabilityBuilder,
  ) {
    return new NamedTypeBuilderImpl.forInvalidType(
      token.lexeme,
      nullabilityBuilder,
      message.withLocation(_fileUri, fileOffset, noLength),
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression qualifiedLookup(Token name) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression invokeConstructor(
    List<TypeBuilder>? typeArguments,
    String name,
    ArgumentsImpl arguments,
    Token nameToken,
    Token nameLastToken,
    Constness constness, {
    required bool inImplicitCreationContext,
  }) {
    return buildProblem();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    return new IndexedAccessGenerator(
      _helper,
      token,
      buildSimpleRead(),
      index,
      isNullAware: isNullAware,
    );
  }
}

/// A [ThisAccessGenerator] represents a subexpression whose prefix is `this`
/// or `super`.
///
/// For instance
///
///   class C {
///     var b = this.c; // a ThisAccessGenerator is created for `this`.
///     var c;
///     C(this.c) :     // a ThisAccessGenerator is created for `this`.
///       this.b = c;   // a ThisAccessGenerator is created for `this`.
///     method() {
///       this.b;       // a ThisAccessGenerator is created for `this`.
///       super.b();    // a ThisAccessGenerator is created for `super`.
///       this.b = c;   // a ThisAccessGenerator is created for `this`.
///       this.b += c;  // a ThisAccessGenerator is created for `this`.
///     }
///   }
///
/// If this `this` occurs in an instance member on an extension declaration,
/// a [ReadOnlyAccessGenerator] is created instead.
///
class ThisAccessGenerator extends Generator {
  /// `true` if this access is in an initializer list.
  ///
  /// For instance in `<init>` in
  ///
  ///    class Class {
  ///      Class() : <init>;
  ///    }
  ///
  final bool isInitializer;

  /// `true` if this access is in a field initializer either directly or within
  /// an initializer list.
  ///
  /// For instance in `<init>` in
  ///
  ///    var foo = <init>;
  ///    class Class {
  ///      var bar = <init>;
  ///      Class() : <init>;
  ///    }
  ///
  final bool inFieldInitializer;

  /// `true` if this access is directly in a field initializer of a late field.
  ///
  /// For instance in `<init>` in
  ///
  ///    late var foo = <init>;
  ///    class Class {
  ///      late var bar = <init>;
  ///      Class() : bar = 42;
  ///    }
  ///
  final bool inLateFieldInitializer;

  /// `true` if this subexpression represents a `super` prefix.
  final bool isSuper;

  ThisAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.isInitializer,
    this.inFieldInitializer,
    this.inLateFieldInitializer, {
    this.isSuper = false,
  }) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead {
    return unsupported(
      "${isSuper ? 'super' : 'this'}.plainNameForRead",
      fileOffset,
      _fileUri,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ThisAccessGenerator";

  @override
  Expression buildSimpleRead() {
    if (!isSuper) {
      if (inFieldInitializer && !inLateFieldInitializer) {
        return buildFieldInitializerError(null);
      } else {
        return _forest.createThisExpression(fileOffset);
      }
    } else {
      return _helper.buildProblem(
        message: codeSuperAsExpression,
        fileUri: _helper.uri,
        fileOffset: fileOffset,
        length: lengthForToken(token),
      );
    }
  }

  InvalidExpression buildFieldInitializerError(
    Map<String, int>? initializedFields,
  ) {
    String keyword = isSuper ? "super" : "this";
    return _helper.buildProblem(
      message: codeThisOrSuperAccessInFieldInitializer.withArgumentsOld(
        keyword,
      ),
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: keyword.length,
    );
  }

  @override
  List<Initializer> buildFieldInitializer(Map<String, int>? initializedFields) {
    InvalidExpression error = buildFieldInitializerError(initializedFields);
    return <Initializer>[_helper.buildInvalidInitializer(error)];
  }

  @override
  Expression_Generator_Initializer buildSelectorAccess(
    Selector selector,
    int operatorOffset,
    bool isNullAware,
  ) {
    Name name = selector.name;
    ArgumentsImpl? arguments = selector.arguments;
    int offset = offsetForToken(selector.token);
    if (isInitializer && selector is InvocationSelector) {
      if (isNullAware) {
        problemReporting.addProblem(
          codeInvalidUseOfNullAwareAccess,
          operatorOffset,
          2,
          _fileUri,
        );
      }
      return buildConstructorInitializer(offset, name, arguments!);
    }
    selector.reportNewAsSelector();
    if (inFieldInitializer && !inLateFieldInitializer && !isInitializer) {
      return buildFieldInitializerError(null);
    }
    if (selector is InvocationSelector) {
      // Notice that 'this' or 'super' can't be null. So we can ignore the
      // value of [isNullAware].
      if (isSuper) {
        return _helper.buildSuperInvocation(
          name,
          selector.arguments,
          offsetForToken(selector.token),
        );
      } else {
        return _helper.buildMethodInvocation(
          _forest.createThisExpression(fileOffset),
          name,
          selector.arguments,
          offsetForToken(selector.token),
        );
      }
    } else {
      if (isSuper) {
        Member? getter = _helper.lookupSuperMember(name);
        Member? setter = _helper.lookupSuperMember(name, isSetter: true);
        return new SuperPropertyAccessGenerator(
          _helper,
          // TODO(ahe): This is not the 'super' token.
          selector.token,
          name,
          getter,
          setter,
        );
      } else {
        return new ThisPropertyAccessGenerator(
          _helper,
          // TODO(ahe): This is not the 'this' token.
          selector.token,
          name,
          thisVariable: null,
          thisOffset: fileOffset,
          isNullAware: isNullAware,
        );
      }
    }
  }

  @override
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    if (isInitializer) {
      return buildConstructorInitializer(offset, new Name(""), arguments);
    } else if (isSuper) {
      return _helper.buildSuperInvocation(
        Name.callName,
        arguments,
        offset,
        isImplicitCall: true,
      );
    } else {
      return _helper.forest.createExpressionInvocation(
        offset,
        _forest.createThisExpression(fileOffset),
        arguments,
      );
    }
  }

  @override
  Expression_Generator buildEqualsOperation(
    Token token,
    Expression right, {
    required bool isNot,
  }) {
    if (isSuper) {
      int offset = offsetForToken(token);
      Expression result = _helper.buildSuperInvocation(
        equalsName,
        _forest.createArguments(offset, <Expression>[right]),
        offset,
      );
      if (isNot) {
        result = _forest.createNot(offset, result);
      }
      return result;
    }
    // Coverage-ignore(suite): Not run.
    return super.buildEqualsOperation(token, right, isNot: isNot);
  }

  @override
  Expression_Generator buildBinaryOperation(
    Token token,
    Name binaryName,
    Expression right,
  ) {
    if (isSuper) {
      int offset = offsetForToken(token);
      return _helper.buildSuperInvocation(
        binaryName,
        _forest.createArguments(offset, <Expression>[right]),
        offset,
      );
    }
    // Coverage-ignore(suite): Not run.
    return super.buildBinaryOperation(token, binaryName, right);
  }

  @override
  Expression_Generator buildUnaryOperation(Token token, Name unaryName) {
    if (isSuper) {
      int offset = offsetForToken(token);
      return _helper.buildSuperInvocation(
        unaryName,
        _forest.createArgumentsEmpty(offset),
        offset,
      );
    }
    // Coverage-ignore(suite): Not run.
    return super.buildUnaryOperation(token, unaryName);
  }

  Expression_Initializer buildConstructorInitializer(
    int offset,
    Name name,
    ArgumentsImpl arguments,
  ) {
    if (isSuper) {
      MemberLookupResult? result = _helper.lookupSuperConstructor(
        name.text,
        _helper.libraryBuilder,
      );
      Constructor? constructor;
      if (result != null) {
        if (result.isInvalidLookup) {
          return _helper.buildInvalidInitializer(
            LookupResult.createDuplicateExpression(
              result,
              context: _helper.libraryBuilder.loader.target.context,
              name: name.text,
              fileUri: _helper.uri,
              fileOffset: offset,
              length: noLength,
            ),
          );
        }
        MemberBuilder? memberBuilder = result.getable;
        Member? member = memberBuilder?.invokeTarget;
        // TODO(johnniwinther): Passing the library builder to
        // `lookupSuperConstructor` doesn't correctly account for privacy
        // checking when the target class is a mixin application. In this case
        // the constructor name space can include private constructors from
        // another library but the mixin application itself is the current
        // library. Change `lookupSuperConstructor` to avoid this deficiency.
        if (member is Constructor &&
            member.name.libraryReference == name.libraryReference) {
          constructor = member;
        }
      }
      if (constructor == null) {
        String fullName = _helper.superConstructorNameForDiagnostics(name.text);
        return _helper.buildInvalidInitializer(
          _helper.buildProblem(
            message: codeSuperclassHasNoConstructor.withArgumentsOld(fullName),
            fileUri: _fileUri,
            fileOffset: fileOffset,
            length: lengthForToken(token),
          ),
        );
      } else {
        return _helper.buildSuperInitializer(
          false,
          constructor,
          arguments,
          offset,
        );
      }
    } else {
      return _helper.buildRedirectingInitializer(
        name,
        arguments,
        fileOffset: offset,
      );
    }
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return buildAssignmentError();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    return buildAssignmentError();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    return buildAssignmentError();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPrefixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildAssignmentError();
  }

  @override
  // Coverage-ignore(suite): Not run.
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    return buildAssignmentError();
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    if (isSuper) {
      return new SuperIndexedAccessGenerator(
        _helper,
        token,
        index,
        _helper.lookupSuperMember(indexGetName) as Procedure?,
        _helper.lookupSuperMember(indexSetName) as Procedure?,
      );
    } else {
      return new ThisIndexedAccessGenerator(
        _helper,
        token,
        index,
        thisOffset: fileOffset,
        isNullAware: isNullAware,
      );
    }
  }

  // Coverage-ignore(suite): Not run.
  Expression buildAssignmentError() {
    return _helper.buildProblem(
      message: isSuper ? codeCannotAssignToSuper : codeNotAnLvalue,
      fileUri: _helper.uri,
      fileOffset: fileOffset,
      length: token.length,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", isInitializer: ");
    sink.write(isInitializer);
    sink.write(", inFieldInitializer: ");
    sink.write(inFieldInitializer);
    sink.write(", inLateFieldInitializer: ");
    sink.write(inLateFieldInitializer);
    sink.write(", isSuper: ");
    sink.write(isSuper);
  }
}

class IncompleteErrorGenerator extends ErroneousExpressionGenerator {
  final Message message;

  IncompleteErrorGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.message,
  ) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _plainNameForRead => token.lexeme;

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "IncompleteErrorGenerator";

  @override
  InvalidExpression buildError({
    Arguments? arguments,
    required UnresolvedKind kind,
    String? name,
    int? charOffset,
    int? charLength,
    bool errorHasBeenReported = false,
  }) {
    if (charOffset == null) {
      charOffset = fileOffset;
      charLength ??= lengthForToken(token);
    }
    charLength ??= noLength;
    return _helper.buildProblem(
      message: message,
      fileUri: _helper.uri,
      fileOffset: charOffset,
      length: charLength,
      errorHasBeenReported: errorHasBeenReported,
    );
  }

  @override
  Generator doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) => this;

  @override
  Expression buildSimpleRead() {
    return buildError(kind: UnresolvedKind.Member)..fileOffset = fileOffset;
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", message: ");
    sink.write(message.code.name);
  }
}

/// [ParenthesizedExpressionGenerator] represents the subexpression whose prefix
/// is a parenthesized expression.
///
/// For instance:
///
///   method(final a) {
///     final b = null;
///     (a);           // this generator is created for `(a)`.
///     (a)[];         // this generator is created for `(a)`.
///     (b)();         // this generator is created for `(b)`.
///     (b).c = (a.d); // this generator is created for `(a.d)` and `(b)`.
///   }
///
// TODO(johnniwinther): Remove this in favor of [ParenthesizedExpression] when
// the [TypePromoter] is replaced by [FlowAnalysis].
class ParenthesizedExpressionGenerator extends AbstractReadOnlyAccessGenerator {
  @override
  final Expression expression;

  ParenthesizedExpressionGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.expression,
  ) : super(helper, token, ReadOnlyAccessKind.ParenthesizedExpression);

  @override
  String get targetName => '';

  @override
  Expression buildSimpleRead() => _createRead();

  @override
  Expression _createRead() =>
      _helper.forest.createParenthesized(expression.fileOffset, expression);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => "ParenthesizedExpressionGenerator";

  @override
  Expression_Generator buildSelectorAccess(
    Selector selector,
    int operatorOffset,
    bool isNullAware,
  ) {
    selector.reportNewAsSelector();
    if (selector is InvocationSelector) {
      return _helper.buildMethodInvocation(
        _createRead(),
        selector.name,
        selector.arguments,
        offsetForToken(selector.token),
        isNullAware: isNullAware,
        isConstantExpression: selector.isPotentiallyConstant,
      );
    } else {
      if (_helper.constantContext != ConstantContext.none &&
          // Coverage-ignore(suite): Not run.
          selector.name != lengthName) {
        // Coverage-ignore-block(suite): Not run.
        problemReporting.addProblem(
          codeNotAConstantExpression,
          fileOffset,
          token.length,
          _fileUri,
        );
      }
      return PropertyAccessGenerator.make(
        _helper,
        selector.token,
        _createRead(),
        selector.name,
        isNullAware,
      );
    }
  }
}

int adjustForImplicitCall(String? name, int offset) {
  // Normally the offset is at the start of the token, but in this case,
  // because we insert a '.call', we want it at the end instead.
  return offset + (name?.length ?? 0);
}

bool isFieldOrGetter(Member? member) {
  return member is Field || (member is Procedure && member.isGetter);
}

/// A [Selector] is a part of an object access after `.` or `..` or `?.`,
/// including arguments, if present.
///
/// For instance, an [InvocationSelector] is created for `b()` in
///
///    a.b();
///    a..b();
///    a?.b();
///
/// and a [PropertySelector] is created for `b` in
///
///    a.b;
///    a.b = c;
///    a..b;
///    a..b = c;
///    a?.b;
///    a?.b = c;
///
abstract class Selector {
  final ExpressionGeneratorHelper _helper;
  final Token token;

  Selector(this._helper, this.token);

  int get fileOffset => offsetForToken(token);

  Name get name;

  /// Applies this selector to [receiver].
  Expression_Generator withReceiver(
    Object? receiver,
    int operatorOffset, {
    bool isNullAware = false,
  });

  List<TypeBuilder>? get typeArguments => null;

  // Coverage-ignore(suite): Not run.
  bool get isTypeArgumentsInForest => true;

  ArgumentsImpl? get arguments => null;

  /// Internal name used for debugging.
  String get _debugName;

  void printOn(StringSink sink);

  /// Report an error if the selector name "new" when the constructor-tearoff
  /// feature is enabled.
  void reportNewAsSelector() {
    if (name.text == 'new' &&
        _helper.libraryFeatures.constructorTearoffs.isEnabled) {
      _helper.problemReporting.addProblem(
        codeNewAsSelector,
        fileOffset,
        name.text.length,
        _helper.uri,
      );
    }
  }

  @override
  String toString() {
    StringBuffer buffer = new StringBuffer();
    buffer.write(_debugName);
    buffer.write("(offset: ");
    buffer.write("${fileOffset}");
    printOn(buffer);
    buffer.write(")");
    return "$buffer";
  }
}

/// An [InvocationSelector] is the part of an object invocation after `.` or
/// `..` or `?.` including arguments.
///
/// For instance, an [InvocationSelector] is created for `b()` in
///
///    a.b();
///    a..b();
///    a?.b();
///
class InvocationSelector extends Selector {
  @override
  final Name name;

  @override
  final List<TypeBuilder>? typeArguments;

  @override
  final bool isTypeArgumentsInForest;

  @override
  final ArgumentsImpl arguments;

  final bool isPotentiallyConstant;

  InvocationSelector(
    ExpressionGeneratorHelper helper,
    Token token,
    this.name,
    this.typeArguments,
    this.arguments, {
    this.isPotentiallyConstant = false,
    this.isTypeArgumentsInForest = true,
  }) : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => 'InvocationSelector';

  @override
  Expression_Generator withReceiver(
    Object? receiver,
    int operatorOffset, {
    bool isNullAware = false,
  }) {
    if (receiver is Generator) {
      return receiver.buildSelectorAccess(this, operatorOffset, isNullAware);
    }
    reportNewAsSelector();
    return _helper.buildMethodInvocation(
      _helper.toValue(receiver),
      name,
      arguments,
      fileOffset,
      isNullAware: isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", name: ");
    sink.write(name.text);
    sink.write(", arguments: ");
    printNodeOn(arguments, sink);
  }
}

/// A [PropertySelector] is the part of an object access after `.` or `..` or
/// `?.`.
///
/// For instance a [PropertySelector] is created for `b` in
///
///    a.b;
///    a.b = c;
///    a..b;
///    a..b = c;
///    a?.b;
///    a?.b = c;
///
class PropertySelector extends Selector {
  @override
  final Name name;

  PropertySelector(ExpressionGeneratorHelper helper, Token token, this.name)
    : super(helper, token);

  @override
  // Coverage-ignore(suite): Not run.
  String get _debugName => 'PropertySelector';

  @override
  Expression_Generator withReceiver(
    Object? receiver,
    int operatorOffset, {
    bool isNullAware = false,
  }) {
    if (receiver is Generator) {
      return receiver.buildSelectorAccess(this, operatorOffset, isNullAware);
    }
    reportNewAsSelector();
    return PropertyAccessGenerator.make(
      _helper,
      token,
      _helper.toValue(receiver),
      name,
      isNullAware,
    );
  }

  @override
  // Coverage-ignore(suite): Not run.
  void printOn(StringSink sink) {
    sink.write(", name: ");
    sink.write(name.text);
  }
}

// Coverage-ignore(suite): Not run.
class AugmentSuperAccessGenerator extends Generator {
  final AugmentSuperTarget augmentSuperTarget;

  AugmentSuperAccessGenerator(
    ExpressionGeneratorHelper helper,
    Token token,
    this.augmentSuperTarget,
  ) : super(helper, token);

  @override
  String get _debugName => "AugmentSuperGenerator";

  @override
  String get _plainNameForRead {
    return unsupported("augment super.plainNameForRead", fileOffset, _fileUri);
  }

  Expression _createRead() {
    Member? readTarget = augmentSuperTarget.readTarget;
    if (readTarget != null) {
      return new AugmentSuperGet(readTarget, fileOffset: fileOffset);
    } else {
      return _helper.buildProblem(
        message: codeNoAugmentSuperReadTarget,
        fileUri: _helper.uri,
        fileOffset: fileOffset,
        length: noLength,
      );
    }
  }

  @override
  Expression buildAssignment(Expression value, {bool voidContext = false}) {
    return _createWrite(fileOffset, value, forEffect: voidContext);
  }

  Expression _createWrite(
    int offset,
    Expression value, {
    required bool forEffect,
  }) {
    Member? writeTarget = augmentSuperTarget.writeTarget;
    if (writeTarget != null) {
      return new AugmentSuperSet(
        writeTarget,
        value,
        forEffect: forEffect,
        fileOffset: fileOffset,
      );
    } else {
      return _helper.buildProblem(
        message: codeNoAugmentSuperWriteTarget,
        fileUri: _helper.uri,
        fileOffset: offset,
        length: noLength,
      );
    }
  }

  @override
  Expression buildCompoundAssignment(
    Name binaryOperator,
    Expression value, {
    required int operatorOffset,
    bool voidContext = false,
    bool isPreIncDec = false,
    bool isPostIncDec = false,
  }) {
    // TODO(johnniwinther): Is this ever valid? Augment getters have no access
    // to the augmented setter, augmenting setters have no access to the
    // augmented getters, and augmenting fields only have read access to the
    // augmented field initializer expression.

    Expression binary = _helper.forest.createBinary(
      operatorOffset,
      _createRead(),
      binaryOperator,
      value,
    );
    return _createWrite(fileOffset, binary, forEffect: voidContext);
  }

  @override
  Expression buildIfNullAssignment(
    Expression value,
    DartType type,
    int offset, {
    bool voidContext = false,
  }) {
    // TODO(johnniwinther): Is this ever valid? Augment getters have no access
    // to the augmented setter, augmenting setters have no access to the
    // augmented getters, and augmenting fields only have read access to the
    // augmented field initializer expression.
    return new IfNullSet(
      _createRead(),
      _createWrite(offset, value, forEffect: voidContext),
      forEffect: voidContext,
    )..fileOffset = offset;
  }

  @override
  Generator buildIndexedAccess(
    Expression index,
    Token token, {
    required bool isNullAware,
  }) {
    // TODO(johnniwinther): The semantics is unclear. Is this accessing the
    // invoke target, which must be an `operator []` or the read target with a
    // type that has an `operator []`.
    throw new UnimplementedError();
  }

  @override
  Expression buildPostfixIncrement(
    Name binaryOperator, {
    required int operatorOffset,
    bool voidContext = false,
  }) {
    // TODO(johnniwinther): Is this ever valid? Augment getters have no access
    // to the augmented setter, augmenting setters have no access to the
    // augmented getters, and augmenting fields only have read access to the
    // augmented field initializer expression.
    throw new UnimplementedError();
  }

  @override
  Expression buildSimpleRead() {
    return _createRead();
  }

  @override
  Expression_Generator_Initializer doInvocation(
    int offset,
    List<TypeBuilder>? typeArguments,
    ArgumentsImpl arguments, {
    bool isTypeArgumentsInForest = false,
  }) {
    Member? invokeTarget = augmentSuperTarget.invokeTarget;
    if (invokeTarget != null) {
      return new AugmentSuperInvocation(
        invokeTarget,
        arguments,
        fileOffset: fileOffset,
      );
    } else {
      return _helper.buildProblem(
        message: codeNoAugmentSuperInvokeTarget,
        fileUri: _helper.uri,
        fileOffset: offset,
        length: noLength,
      );
    }
  }

  @override
  void printOn(StringSink sink) {
    sink.write(", augmentSuperTarget: ");
    sink.write(augmentSuperTarget);
  }
}
